import java.util.Collections; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.event.server.ServerListPingEvent; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; import com.google.common.collect.Lists; import com.google.common.collect.MapMaker; import com.mojang.authlib.GameProfile; import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; //te importy odnoszą się do tej klasy, do zmienienia tylko secondattempt na package w której jest klasa import secondattempt.MOTDManager.Reflection.ConstructorInvoker; import secondattempt.MOTDManager.Reflection.FieldAccessor; import secondattempt.MOTDManager.Reflection.MethodInvoker; @SuppressWarnings({ "unchecked" }) public class MOTDManager implements Listener{ public MOTDManager(Plugin pl) { if(pl != null) { this.plugin = pl; Bukkit.getServer().getPluginManager().registerEvents(this, pl); } } Plugin plugin; public Class serverInfoClass = getNMSClass("PacketStatusOutServerInfo"); public Class cs = getNMSClass("ChatSerializer"); public Class serverPingClass = (Class) getNMSClass("ServerPing"); public Class getMotdClass = (Class) getNMSClass("IChatBaseComponent"); public Class setMotdClass = (Class) getNMSClass("ChatComponentText"); public Class getServerClass = (Class) getNMSClass("ServerData"); public Class getplayerClass = (Class) getNMSClass("ServerPingPlayerSample"); public FieldAccessor serverPing = Reflection.getField(serverInfoClass, serverPingClass, 0); public FieldAccessor player = Reflection.getField(serverPingClass, getplayerClass, 0); public FieldAccessor motdClass = Reflection.getField(serverPingClass, getMotdClass, 0); public FieldAccessor serverClass = Reflection.getField(serverPingClass, getServerClass, 0); public ConstructorInvoker motdInvoker = Reflection.getConstructor(setMotdClass, String.class); public ConstructorInvoker serverInvoker = Reflection.getConstructor(getServerClass, String.class, int.class); @EventHandler public void eva(ServerListPingEvent event) { new TinyProtocol(plugin) { @Override public Object onPacketOutAsync(Player reciever, Channel channel, Object packet) { if (serverInfoClass.isInstance(packet)) { Object ping = serverPing.get(packet); Object playersample = player.get(ping); Object serverdata = serverClass.get(ping); int baseMaxPlayers = (int) Reflection.getMethod(playersample.getClass(), "a").invoke(playersample); int baseOnlinePlayers = (int) Reflection.getMethod(playersample.getClass(), "b").invoke(playersample); GameProfile[] baseHoverList = (GameProfile[]) Reflection.getMethod(playersample.getClass(), "c").invoke(playersample); String baseMOTD = (String) Reflection.getMethod(cs, "a", getMotdClass).invoke(cs, Reflection.getMethod(ping.getClass(), "a").invoke(ping)); baseMOTD = (String) baseMOTD.subSequence(9, baseMOTD.length() - 2); String baseVersion = (String) Reflection.getMethod(serverdata.getClass(), "a").invoke(serverdata); ServerPingEvent ev = new ServerPingEvent(event.getAddress(), baseMOTD, baseHoverList, baseOnlinePlayers, baseMaxPlayers, baseVersion); Bukkit.getServer().getPluginManager().callEvent(ev); Reflection.getField(playersample.getClass(), "a", int.class).set(playersample, ev.getMax()); Reflection.getField(playersample.getClass(), "b", int.class).set(playersample, ev.getOnline()); Reflection.getField(playersample.getClass(), "c", GameProfile[].class).set(playersample, getHover(ev.getHover())); motdClass.set(ping, motdInvoker.invoke(color(ev.getMotdLine1() + "\n" + ev.getMotdLine2()))); serverClass.set(ping, serverInvoker.invoke(color(ev.getVersion()), 0)); return packet; } return packet; } }; } private String color(String str) { return ChatColor.translateAlternateColorCodes('&', str); } private GameProfile getGP(String s) { return new GameProfile(new UUID(0, 0), color(s)); } private GameProfile[] getHover(List list) { if(list == null || list.size() == 0) return new GameProfile[] {getGP("")}; GameProfile[] gameProfiles = new GameProfile[list.size()]; int i = 0; for(String line : list) { if(line == null) { gameProfiles[i] = getGP(""); } else { gameProfiles[i] = getGP(line); } i++; } return gameProfiles; } private Class getNMSClass(String name) { String nms = Bukkit.getServer().getClass().getPackage().getName(); nms = "net.minecraft.server." + nms.substring(nms.lastIndexOf('.') + 1); try { if(name.equals("ServerData")) { return Class.forName(nms + ".ServerPing$ServerData"); } else if(name.equals("ServerPingPlayerSample")) { return Class.forName(nms + ".ServerPing$ServerPingPlayerSample"); } else if(name.equals("PacketStatusOutServerInfo")) { return Class.forName(nms + ".PacketStatusOutServerInfo"); } else if(name.equals("ServerPing")) { return Class.forName(nms + ".ServerPing"); } else if(name.equals("IChatBaseComponent")) { return Class.forName(nms + ".IChatBaseComponent"); } else if(name.equals("ChatComponentText")) { return Class.forName(nms + ".ChatComponentText"); } else if(name.equals("ChatSerializer")) { return Class.forName(nms + ".IChatBaseComponent$ChatSerializer"); } } catch(ClassNotFoundException ex) { System.out.println("BLAD KRYTYCZNY DLA: " + name); } return null; } /** * Represents a very tiny alternative to ProtocolLib. *

* It now supports intercepting packets during login and status ping (such as OUT_SERVER_PING)! * * @author Kristian */ private static abstract class TinyProtocol { private static final AtomicInteger ID = new AtomicInteger(0); // Used in order to lookup a channel private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle"); private static final FieldAccessor getConnection = Reflection.getField("{nms}.EntityPlayer", "playerConnection", Object.class); private static final FieldAccessor getManager = Reflection.getField("{nms}.PlayerConnection", "networkManager", Object.class); private static final FieldAccessor getChannel = Reflection.getField("{nms}.NetworkManager", Channel.class, 0); // Looking up ServerConnection private static final Class minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer"); private static final Class serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection"); private static final FieldAccessor getMinecraftServer = Reflection.getField("{obc}.CraftServer", minecraftServerClass, 0); private static final FieldAccessor getServerConnection = Reflection.getField(minecraftServerClass, serverConnectionClass, 0); private static final MethodInvoker getNetworkMarkers = Reflection.getTypedMethod(serverConnectionClass, null, List.class, serverConnectionClass); // Packets we have to intercept private static final Class PACKET_LOGIN_IN_START = Reflection.getMinecraftClass("PacketLoginInStart"); private static final FieldAccessor getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0); // Speedup channel lookup private Map channelLookup = new MapMaker().weakValues().makeMap(); private Listener listener; // Channels that have already been removed private Set uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap()); // List of network markers private List networkManagers; // Injected channel handlers private List serverChannels = Lists.newArrayList(); private ChannelInboundHandlerAdapter serverChannelHandler; private ChannelInitializer beginInitProtocol; private ChannelInitializer endInitProtocol; // Current handler name private String handlerName; protected volatile boolean closed; protected Plugin plugin; /** * Construct a new instance of TinyProtocol, and start intercepting packets for all connected clients and future clients. *

* You can construct multiple instances per plugin. * * @param plugin - the plugin. */ public TinyProtocol(final Plugin plugin) { this.plugin = plugin; // Compute handler name this.handlerName = getHandlerName(); // Prepare existing players registerBukkitEvents(); try { registerChannelHandler(); registerPlayers(plugin); } catch (IllegalArgumentException ex) { // Damn you, late bind new BukkitRunnable() { @Override public void run() { registerChannelHandler(); registerPlayers(plugin); } }.runTask(plugin); } } private void createServerChannelHandler() { // Handle connected channels endInitProtocol = new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { try { // This can take a while, so we need to stop the main thread from interfering synchronized (networkManagers) { // Stop injecting channels if (!closed) { channel.eventLoop().submit(() -> injectChannelInternal(channel)); } } } catch (Exception e) { plugin.getLogger().log(Level.SEVERE, "Cannot inject incomming channel " + channel, e); } } }; // This is executed before Minecraft's channel handler beginInitProtocol = new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { channel.pipeline().addLast(endInitProtocol); } }; serverChannelHandler = new ChannelInboundHandlerAdapter() { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { Channel channel = (Channel) msg; // Prepare to initialize ths channel channel.pipeline().addFirst(beginInitProtocol); ctx.fireChannelRead(msg); } }; } /** * Register bukkit events. */ private void registerBukkitEvents() { listener = new Listener() { @EventHandler(priority = EventPriority.LOWEST) public final void onPlayerLogin(PlayerLoginEvent e) { if (closed) return; Channel channel = getChannel(e.getPlayer()); // Don't inject players that have been explicitly uninjected if (!uninjectedChannels.contains(channel)) { injectPlayer(e.getPlayer()); } } @EventHandler public final void onPluginDisable(PluginDisableEvent e) { if (e.getPlugin().equals(plugin)) { close(); } } }; plugin.getServer().getPluginManager().registerEvents(listener, plugin); } private void registerChannelHandler() { Object mcServer = getMinecraftServer.get(Bukkit.getServer()); Object serverConnection = getServerConnection.get(mcServer); boolean looking = true; // We need to synchronize against this list networkManagers = (List) getNetworkMarkers.invoke(null, serverConnection); createServerChannelHandler(); // Find the correct list, or implicitly throw an exception for (int i = 0; looking; i++) { List list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection); for (Object item : list) { if (!ChannelFuture.class.isInstance(item)) break; // Channel future that contains the server connection Channel serverChannel = ((ChannelFuture) item).channel(); serverChannels.add(serverChannel); serverChannel.pipeline().addFirst(serverChannelHandler); looking = false; } } } private void unregisterChannelHandler() { if (serverChannelHandler == null) return; for (Channel serverChannel : serverChannels) { final ChannelPipeline pipeline = serverChannel.pipeline(); // Remove channel handler serverChannel.eventLoop().execute(new Runnable() { @Override public void run() { try { pipeline.remove(serverChannelHandler); } catch (NoSuchElementException e) { // That's fine } } }); } } private void registerPlayers(Plugin plugin) { for (Player player : plugin.getServer().getOnlinePlayers()) { injectPlayer(player); } } /** * Invoked when the server is starting to send a packet to a player. *

* Note that this is not executed on the main thread. * * @param receiver - the receiving player, NULL for early login/status packets. * @param channel - the channel that received the packet. Never NULL. * @param packet - the packet being sent. * @return The packet to send instead, or NULL to cancel the transmission. */ public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) { return packet; } /** * Invoked when the server has received a packet from a given player. *

* Use {@link Channel#remoteAddress()} to get the remote address of the client. * * @param sender - the player that sent the packet, NULL for early login/status packets. * @param channel - channel that received the packet. Never NULL. * @param packet - the packet being received. * @return The packet to recieve instead, or NULL to cancel. */ public Object onPacketInAsync(Player sender, Channel channel, Object packet) { return packet; } /** * Retrieve the name of the channel injector, default implementation is "tiny-" + plugin name + "-" + a unique ID. *

* Note that this method will only be invoked once. It is no longer necessary to override this to support multiple instances. * * @return A unique channel handler name. */ protected String getHandlerName() { return "tiny-" + plugin.getName() + "-" + ID.incrementAndGet(); } /** * Add a custom channel handler to the given player's channel pipeline, allowing us to intercept sent and received packets. *

* This will automatically be called when a player has logged in. * * @param player - the player to inject. */ public void injectPlayer(Player player) { injectChannelInternal(getChannel(player)).player = player; } /** * Add a custom channel handler to the given channel. * * @param channel - the channel to inject. * @return The packet interceptor. */ private PacketInterceptor injectChannelInternal(Channel channel) { try { PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName); // Inject our packet interceptor if (interceptor == null) { interceptor = new PacketInterceptor(); channel.pipeline().addBefore("packet_handler", handlerName, interceptor); uninjectedChannels.remove(channel); } return interceptor; } catch (IllegalArgumentException e) { // Try again return (PacketInterceptor) channel.pipeline().get(handlerName); } } /** * Retrieve the Netty channel associated with a player. This is cached. * * @param player - the player. * @return The Netty channel. */ public Channel getChannel(Player player) { Channel channel = channelLookup.get(player.getName()); // Lookup channel again if (channel == null) { Object connection = getConnection.get(getPlayerHandle.invoke(player)); Object manager = getManager.get(connection); channelLookup.put(player.getName(), channel = getChannel.get(manager)); } return channel; } /** * Uninject a specific player. * * @param player - the injected player. */ public void uninjectPlayer(Player player) { uninjectChannel(getChannel(player)); } /** * Uninject a specific channel. *

* This will also disable the automatic channel injection that occurs when a player has properly logged in. * * @param channel - the injected channel. */ public void uninjectChannel(final Channel channel) { // No need to guard against this if we're closing if (!closed) { uninjectedChannels.add(channel); } // See ChannelInjector in ProtocolLib, line 590 channel.eventLoop().execute(new Runnable() { @Override public void run() { channel.pipeline().remove(handlerName); } }); } /** * Cease listening for packets. This is called automatically when your plugin is disabled. */ public final void close() { if (!closed) { closed = true; // Remove our handlers for (Player player : plugin.getServer().getOnlinePlayers()) { uninjectPlayer(player); } // Clean up Bukkit HandlerList.unregisterAll(listener); unregisterChannelHandler(); } } /** * Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets. * * @author Kristian */ private final class PacketInterceptor extends ChannelDuplexHandler { // Updated by the login event public volatile Player player; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // Intercept channel final Channel channel = ctx.channel(); handleLoginStart(channel, msg); try { msg = onPacketInAsync(player, channel, msg); } catch (Exception e) { plugin.getLogger().log(Level.SEVERE, "Error in onPacketInAsync().", e); } if (msg != null) { super.channelRead(ctx, msg); } } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { try { msg = onPacketOutAsync(player, ctx.channel(), msg); } catch (Exception e) { plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e); } if (msg != null) { super.write(ctx, msg, promise); } } private void handleLoginStart(Channel channel, Object packet) { if (PACKET_LOGIN_IN_START.isInstance(packet)) { GameProfile profile = getGameProfile.get(packet); channelLookup.put(profile.getName(), channel); } } } } /** * An utility class that simplifies reflection in Bukkit plugins. * * @author Kristian */ public static class Reflection { /** * An interface for invoking a specific constructor. */ public interface ConstructorInvoker { /** * Invoke a constructor for a specific class. * * @param arguments - the arguments to pass to the constructor. * @return The constructed object. */ public Object invoke(Object... arguments); } /** * An interface for invoking a specific method. */ public interface MethodInvoker { /** * Invoke a method on a specific target object. * * @param target - the target object, or NULL for a static method. * @param arguments - the arguments to pass to the method. * @return The return value, or NULL if is void. */ public Object invoke(Object target, Object... arguments); } /** * An interface for retrieving the field content. * * @param - field type. */ public interface FieldAccessor { /** * Retrieve the content of a field. * * @param target - the target object, or NULL for a static field. * @return The value of the field. */ public T get(Object target); /** * Set the content of a field. * * @param target - the target object, or NULL for a static field. * @param value - the new value of the field. */ public void set(Object target, Object value); /** * Determine if the given object has this field. * * @param target - the object to test. * @return TRUE if it does, FALSE otherwise. */ public boolean hasField(Object target); } // Deduce the net.minecraft.server.v* package private static String OBC_PREFIX = Bukkit.getServer().getClass().getPackage().getName(); private static String NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server"); private static String VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", ""); // Variable replacement private static Pattern MATCH_VARIABLE = Pattern.compile("\\{([^\\}]+)\\}"); private Reflection() { // Seal class } /** * Retrieve a field accessor for a specific field type and name. * * @param target - the target type. * @param name - the name of the field, or NULL to ignore. * @param fieldType - a compatible field type. * @return The field accessor. */ public static FieldAccessor getField(Class target, String name, Class fieldType) { return getField(target, name, fieldType, 0); } /** * Retrieve a field accessor for a specific field type and name. * * @param className - lookup name of the class, see {@link #getClass(String)}. * @param name - the name of the field, or NULL to ignore. * @param fieldType - a compatible field type. * @return The field accessor. */ public static FieldAccessor getField(String className, String name, Class fieldType) { return getField(getClass(className), name, fieldType, 0); } /** * Retrieve a field accessor for a specific field type and name. * * @param target - the target type. * @param fieldType - a compatible field type. * @param index - the number of compatible fields to skip. * @return The field accessor. */ public static FieldAccessor getField(Class target, Class fieldType, int index) { return getField(target, null, fieldType, index); } /** * Retrieve a field accessor for a specific field type and name. * * @param className - lookup name of the class, see {@link #getClass(String)}. * @param fieldType - a compatible field type. * @param index - the number of compatible fields to skip. * @return The field accessor. */ public static FieldAccessor getField(String className, Class fieldType, int index) { return getField(getClass(className), fieldType, index); } // Common method private static FieldAccessor getField(Class target, String name, Class fieldType, int index) { for (final Field field : target.getDeclaredFields()) { if ((name == null || field.getName().equals(name)) && fieldType.isAssignableFrom(field.getType()) && index-- <= 0) { field.setAccessible(true); // A function for retrieving a specific field value return new FieldAccessor() { @Override public T get(Object target) { try { return (T) field.get(target); } catch (IllegalAccessException e) { throw new RuntimeException("Cannot access reflection.", e); } } @Override public void set(Object target, Object value) { try { field.set(target, value); } catch (IllegalAccessException e) { throw new RuntimeException("Cannot access reflection.", e); } } @Override public boolean hasField(Object target) { // target instanceof DeclaringClass return field.getDeclaringClass().isAssignableFrom(target.getClass()); } }; } } // Search in parent classes if (target.getSuperclass() != null) return getField(target.getSuperclass(), name, fieldType, index); throw new IllegalArgumentException("Cannot find field with type " + fieldType); } /** * Search for the first publicly and privately defined method of the given name and parameter count. * * @param className - lookup name of the class, see {@link #getClass(String)}. * @param methodName - the method name, or NULL to skip. * @param params - the expected parameters. * @return An object that invokes this specific method. * @throws IllegalStateException If we cannot find this method. */ public static MethodInvoker getMethod(String className, String methodName, Class... params) { return getTypedMethod(getClass(className), methodName, null, params); } /** * Search for the first publicly and privately defined method of the given name and parameter count. * * @param clazz - a class to start with. * @param methodName - the method name, or NULL to skip. * @param params - the expected parameters. * @return An object that invokes this specific method. * @throws IllegalStateException If we cannot find this method. */ public static MethodInvoker getMethod(Class clazz, String methodName, Class... params) { return getTypedMethod(clazz, methodName, null, params); } /** * Search for the first publicly and privately defined method of the given name and parameter count. * * @param clazz - a class to start with. * @param methodName - the method name, or NULL to skip. * @param returnType - the expected return type, or NULL to ignore. * @param params - the expected parameters. * @return An object that invokes this specific method. * @throws IllegalStateException If we cannot find this method. */ public static MethodInvoker getTypedMethod(Class clazz, String methodName, Class returnType, Class... params) { for (final Method method : clazz.getDeclaredMethods()) { if ((methodName == null || method.getName().equals(methodName)) && (returnType == null || method.getReturnType().equals(returnType)) && Arrays.equals(method.getParameterTypes(), params)) { method.setAccessible(true); return new MethodInvoker() { @Override public Object invoke(Object target, Object... arguments) { try { return method.invoke(target, arguments); } catch (Exception e) { throw new RuntimeException("Cannot invoke method " + method, e); } } }; } } // Search in every superclass if (clazz.getSuperclass() != null) return getMethod(clazz.getSuperclass(), methodName, params); throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params))); } /** * Search for the first publically and privately defined constructor of the given name and parameter count. * * @param className - lookup name of the class, see {@link #getClass(String)}. * @param params - the expected parameters. * @return An object that invokes this constructor. * @throws IllegalStateException If we cannot find this method. */ public static ConstructorInvoker getConstructor(String className, Class... params) { return getConstructor(getClass(className), params); } /** * Search for the first publically and privately defined constructor of the given name and parameter count. * * @param clazz - a class to start with. * @param params - the expected parameters. * @return An object that invokes this constructor. * @throws IllegalStateException If we cannot find this method. */ public static ConstructorInvoker getConstructor(Class clazz, Class... params) { for (final Constructor constructor : clazz.getDeclaredConstructors()) { if (Arrays.equals(constructor.getParameterTypes(), params)) { constructor.setAccessible(true); return new ConstructorInvoker() { @Override public Object invoke(Object... arguments) { try { return constructor.newInstance(arguments); } catch (Exception e) { throw new RuntimeException("Cannot invoke constructor " + constructor, e); } } }; } } throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params))); } /** * Retrieve a class from its full name, without knowing its type on compile time. *

* This is useful when looking up fields by a NMS or OBC type. *

* * @see {@link #getClass()} for more information. * @param lookupName - the class name with variables. * @return The class. */ public static Class getUntypedClass(String lookupName) { Class clazz = (Class) getClass(lookupName); return clazz; } /** * Retrieve a class from its full name. *

* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table: *

* * * * * * * * * * * * * * * * * *
VariableContent
{nms}Actual package name of net.minecraft.server.VERSION
{obc}Actual pacakge name of org.bukkit.craftbukkit.VERSION
{version}The current Minecraft package VERSION, if any.
* * @param lookupName - the class name with variables. * @return The looked up class. * @throws IllegalArgumentException If a variable or class could not be found. */ public static Class getClass(String lookupName) { return getCanonicalClass(expandVariables(lookupName)); } /** * Retrieve a class in the net.minecraft.server.VERSION.* package. * * @param name - the name of the class, excluding the package. * @throws IllegalArgumentException If the class doesn't exist. */ public static Class getMinecraftClass(String name) { return getCanonicalClass(NMS_PREFIX + "." + name); } /** * Retrieve a class in the org.bukkit.craftbukkit.VERSION.* package. * * @param name - the name of the class, excluding the package. * @throws IllegalArgumentException If the class doesn't exist. */ public static Class getCraftBukkitClass(String name) { return getCanonicalClass(OBC_PREFIX + "." + name); } /** * Retrieve a class by its canonical name. * * @param canonicalName - the canonical name. * @return The class. */ private static Class getCanonicalClass(String canonicalName) { try { return Class.forName(canonicalName); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Cannot find " + canonicalName, e); } } /** * Expand variables such as "{nms}" and "{obc}" to their corresponding packages. * * @param name - the full name of the class. * @return The expanded string. */ private static String expandVariables(String name) { StringBuffer output = new StringBuffer(); Matcher matcher = MATCH_VARIABLE.matcher(name); while (matcher.find()) { String variable = matcher.group(1); String replacement = ""; // Expand all detected variables if ("nms".equalsIgnoreCase(variable)) replacement = NMS_PREFIX; else if ("obc".equalsIgnoreCase(variable)) replacement = OBC_PREFIX; else if ("version".equalsIgnoreCase(variable)) replacement = VERSION; else throw new IllegalArgumentException("Unknown variable: " + variable); // Assume the expanded variables are all packages, and append a dot if (replacement.length() > 0 && matcher.end() < name.length() && name.charAt(matcher.end()) != '.') replacement += "."; matcher.appendReplacement(output, Matcher.quoteReplacement(replacement)); } matcher.appendTail(output); return output.toString(); } } }