// Path: src/main/java/com/github/games647/lagmonitor/threading/BlockingActionManager.java
// public class BlockingActionManager implements Listener {
// 
//     //feel free to improve the wording of this:
//     private static final String THREAD_SAFETY_NOTICE = "As threads **can** run concurrently or in parallel " +
//             "shared data access has to be synchronized (thread-safety) in order to prevent " +
//             "unexpected behavior or crashes. ";
// 
//     private static final String SAFETY_METHODS = "You can guarantee thread-safety by " +
//             "running the data access always on the same thread, using atomic operations, " +
//             "locks (ex: a synchronized block), immutable objects, thread local data " +
//             "or something similar. ";
// 
//     private static final String COMMON_SAFE = "Common things that are thread-safe: Logging, Bukkit Scheduler, " +
//             "Concurrent collections (ex: ConcurrentHashMap or Collections.synchronized*), ... ";
// 
//     private static final String BLOCKING_ACTION_MESSAGE = "Plugin {0} is performing a blocking I/O operation ({1}) " +
//             "on the main thread. " +
//             "This could affect the server performance, because the thread pauses until it gets the response. " +
//             "Such operations should be performed asynchronous from the main thread. " +
//             "Besides gameplay performance it could also improve startup time. " +
//             "Keep in mind to keep the code thread-safe. ";
// 
//     private final Plugin plugin;
// 
//     private final Set<PluginViolation> violations = Sets.newConcurrentHashSet();
//     private final Set<String> violatedPlugins = Sets.newConcurrentHashSet();
// 
//     public BlockingActionManager(Plugin plugin) {
//         this.plugin = plugin;
//     }
// 
//     public void checkBlockingAction(String event) {
//         if (!Bukkit.isPrimaryThread()) {
//             return;
//         }
// 
//         logCurrentStack(BLOCKING_ACTION_MESSAGE, event);
//     }
// 
//     public void checkThreadSafety(String eventName) {
//         if (Bukkit.isPrimaryThread()) {
//             return;
//         }
// 
//         logCurrentStack("Plugin {0} triggered an synchronous event {1} from an asynchronous Thread. ", eventName);
//         plugin.getLogger().info(THREAD_SAFETY_NOTICE);
//         plugin.getLogger().info("Use runTask* (no Async*), scheduleSync* or callSyncMethod to run on the main thread.");
//     }
// 
//     public void logCurrentStack(String format, String eventName) {
//         IllegalAccessException stackTraceCreator = new IllegalAccessException();
//         StackTraceElement[] stackTrace = stackTraceCreator.getStackTrace();
// 
//         Map.Entry<String, StackTraceElement> foundPlugin = findPlugin(stackTrace);
// 
//         PluginViolation violation = new PluginViolation(eventName);
//         if (foundPlugin != null) {
//             String pluginName = foundPlugin.getKey();
//             violation = new PluginViolation(pluginName, foundPlugin.getValue(), eventName);
//             if (!violatedPlugins.add(violation.getPluginName()) && plugin.getConfig().getBoolean("oncePerPlugin")) {
//                 return;
//             }
//         }
// 
//         if (!violations.add(violation)) {
//             return;
//         }
// 
//         plugin.getLogger().log(Level.WARNING, format + "Report it to the plugin author"
//                 , new Object[]{violation.getPluginName(), eventName});
// 
//         if (plugin.getConfig().getBoolean("hideStacktrace")) {
//             plugin.getLogger().log(Level.WARNING, "Source: {0}, method {1}, line {2}"
//                     , new Object[]{violation.getSourceFile(), violation.getMethodName(), violation.getLineNumber()});
//         } else {
//             plugin.getLogger().log(Level.WARNING, "The following exception is not an error. " +
//                     "It's a hint for the plugin developers to find the source. " +
//                     plugin.getName() + " doesn't prevent this action. It just warns you about it. ", stackTraceCreator);
//         }
//     }
// 
//     public Map.Entry<String, StackTraceElement> findPlugin(StackTraceElement[] stacktrace) {
//         boolean skipping = true;
//         for (StackTraceElement elem : stacktrace) {
//             try {
//                 Class<?> clazz = Class.forName(elem.getClassName());
//                 if (clazz.getName().endsWith("VanillaCommandWrapper")) {
//                     //explicit use getName instead of SimpleName because getSimpleBinaryName causes a
//                     //StringIndexOutOfBoundsException for obfuscated plugins
//                     return Maps.immutableEntry("Vanilla", elem);
//                 }
// 
//                 Plugin plugin;
//                 try {
//                     plugin = JavaPlugin.getProvidingPlugin(clazz);
//                     if (plugin == this.plugin) {
//                         continue;
//                     }
// 
//                     return Maps.immutableEntry(plugin.getName(), elem);
//                 } catch (IllegalArgumentException illegalArgumentEx) {
//                     //ignore
//                 }
//             } catch (ClassNotFoundException ex) {
//                 //if this class cannot be loaded then it could be something native so we ignore it
//             }
//         }
// 
//         return null;
//     }
// }
// 
// Path: src/main/java/com/github/games647/lagmonitor/threading/Injectable.java
// public interface Injectable {
// 
//     void inject();
// 
//     void restore();
// }


import com.github.games647.lagmonitor.threading.BlockingActionManager;
import com.github.games647.lagmonitor.threading.Injectable;
import java.io.IOException;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
package com.github.games647.lagmonitor.listener;



public class BlockingConnectionSelector extends ProxySelector implements Injectable {

    private static final Pattern WWW_PATERN = Pattern.compile("www", Pattern.LITERAL);

    private final BlockingActionManager actionManager;
    private ProxySelector oldProxySelector;

    public BlockingConnectionSelector(BlockingActionManager actionManager) {
        this.actionManager = actionManager;
    }

    @Override
    public List<Proxy> select(URI uri) {
        String url = WWW_PATERN.matcher(uri.toString()).replaceAll("");
        if (uri.getScheme().startsWith("http") || (uri.getPort() != 80 && uri.getPort() != 443)) {
// Path: src/main/java/com/github/games647/lagmonitor/threading/BlockingActionManager.java
// public class BlockingActionManager implements Listener {
// 
//     //feel free to improve the wording of this:
//     private static final String THREAD_SAFETY_NOTICE = "As threads **can** run concurrently or in parallel " +
//             "shared data access has to be synchronized (thread-safety) in order to prevent " +
//             "unexpected behavior or crashes. ";
// 
//     private static final String SAFETY_METHODS = "You can guarantee thread-safety by " +
//             "running the data access always on the same thread, using atomic operations, " +
//             "locks (ex: a synchronized block), immutable objects, thread local data " +
//             "or something similar. ";
// 
//     private static final String COMMON_SAFE = "Common things that are thread-safe: Logging, Bukkit Scheduler, " +
//             "Concurrent collections (ex: ConcurrentHashMap or Collections.synchronized*), ... ";
// 
//     private static final String BLOCKING_ACTION_MESSAGE = "Plugin {0} is performing a blocking I/O operation ({1}) " +
//             "on the main thread. " +
//             "This could affect the server performance, because the thread pauses until it gets the response. " +
//             "Such operations should be performed asynchronous from the main thread. " +
//             "Besides gameplay performance it could also improve startup time. " +
//             "Keep in mind to keep the code thread-safe. ";
// 
//     private final Plugin plugin;
// 
//     private final Set<PluginViolation> violations = Sets.newConcurrentHashSet();
//     private final Set<String> violatedPlugins = Sets.newConcurrentHashSet();
// 
//     public BlockingActionManager(Plugin plugin) {
//         this.plugin = plugin;
//     }
// 
//     public void checkBlockingAction(String event) {
//         if (!Bukkit.isPrimaryThread()) {
//             return;
//         }
// 
//         logCurrentStack(BLOCKING_ACTION_MESSAGE, event);
//     }
// 
//     public void checkThreadSafety(String eventName) {
//         if (Bukkit.isPrimaryThread()) {
//             return;
//         }
// 
//         logCurrentStack("Plugin {0} triggered an synchronous event {1} from an asynchronous Thread. ", eventName);
//         plugin.getLogger().info(THREAD_SAFETY_NOTICE);
//         plugin.getLogger().info("Use runTask* (no Async*), scheduleSync* or callSyncMethod to run on the main thread.");
//     }
// 
//     public void logCurrentStack(String format, String eventName) {
//         IllegalAccessException stackTraceCreator = new IllegalAccessException();
//         StackTraceElement[] stackTrace = stackTraceCreator.getStackTrace();
// 
//         Map.Entry<String, StackTraceElement> foundPlugin = findPlugin(stackTrace);
// 
//         PluginViolation violation = new PluginViolation(eventName);
//         if (foundPlugin != null) {
//             String pluginName = foundPlugin.getKey();
//             violation = new PluginViolation(pluginName, foundPlugin.getValue(), eventName);
//             if (!violatedPlugins.add(violation.getPluginName()) && plugin.getConfig().getBoolean("oncePerPlugin")) {
//                 return;
//             }
//         }
// 
//         if (!violations.add(violation)) {
//             return;
//         }
// 
//         plugin.getLogger().log(Level.WARNING, format + "Report it to the plugin author"
//                 , new Object[]{violation.getPluginName(), eventName});
// 
//         if (plugin.getConfig().getBoolean("hideStacktrace")) {
//             plugin.getLogger().log(Level.WARNING, "Source: {0}, method {1}, line {2}"
//                     , new Object[]{violation.getSourceFile(), violation.getMethodName(), violation.getLineNumber()});
//         } else {
//             plugin.getLogger().log(Level.WARNING, "The following exception is not an error. " +
//                     "It's a hint for the plugin developers to find the source. " +
//                     plugin.getName() + " doesn't prevent this action. It just warns you about it. ", stackTraceCreator);
//         }
//     }
// 
//     public Map.Entry<String, StackTraceElement> findPlugin(StackTraceElement[] stacktrace) {
//         boolean skipping = true;
//         for (StackTraceElement elem : stacktrace) {
//             try {
//                 Class<?> clazz = Class.forName(elem.getClassName());
//                 if (clazz.getName().endsWith("VanillaCommandWrapper")) {
//                     //explicit use getName instead of SimpleName because getSimpleBinaryName causes a
//                     //StringIndexOutOfBoundsException for obfuscated plugins
//                     return Maps.immutableEntry("Vanilla", elem);
//                 }
// 
//                 Plugin plugin;
//                 try {
//                     plugin = JavaPlugin.getProvidingPlugin(clazz);
//                     if (plugin == this.plugin) {
//                         continue;
//                     }
// 
//                     return Maps.immutableEntry(plugin.getName(), elem);
//                 } catch (IllegalArgumentException illegalArgumentEx) {
//                     //ignore
//                 }
//             } catch (ClassNotFoundException ex) {
//                 //if this class cannot be loaded then it could be something native so we ignore it
//             }
//         }
// 
//         return null;
//     }
// }
// 
// Path: src/main/java/com/github/games647/lagmonitor/threading/Injectable.java
// public interface Injectable {
// 
//     void inject();
// 
//     void restore();
// }


// Path: src/main/java/com/github/games647/lagmonitor/listener/BlockingConnectionSelector.java
import com.github.games647.lagmonitor.threading.BlockingActionManager;
import com.github.games647.lagmonitor.threading.Injectable;
import java.io.IOException;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

package com.github.games647.lagmonitor.listener;



public class BlockingConnectionSelector extends ProxySelector implements Injectable {

    private static final Pattern WWW_PATERN = Pattern.compile("www", Pattern.LITERAL);

    private final BlockingActionManager actionManager;
    private ProxySelector oldProxySelector;

    public BlockingConnectionSelector(BlockingActionManager actionManager) {
        this.actionManager = actionManager;
    }

    @Override
    public List<Proxy> select(URI uri) {
        String url = WWW_PATERN.matcher(uri.toString()).replaceAll("");
        if (uri.getScheme().startsWith("http") || (uri.getPort() != 80 && uri.getPort() != 443)) {
            actionManager.checkBlockingAction("Socket: " + url);