8035158: Remove dependency on sun.misc.RegexpPool and friends
Reviewed-by: chegar, michaelm
diff --git a/src/share/classes/sun/net/spi/DefaultProxySelector.java b/src/share/classes/sun/net/spi/DefaultProxySelector.java
index f7018ed..36db753 100644
--- a/src/share/classes/sun/net/spi/DefaultProxySelector.java
+++ b/src/share/classes/sun/net/spi/DefaultProxySelector.java
@@ -32,13 +32,14 @@
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.StringTokenizer;
 import java.io.IOException;
-import sun.misc.RegexpPool;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.StringJoiner;
+import java.util.regex.Pattern;
 import sun.net.NetProperties;
 import sun.net.SocksProxy;
+import static java.util.regex.Pattern.quote;
 
 /**
  * Supports proxy settings using system properties This proxy selector
@@ -107,7 +108,7 @@
 
     /**
      * How to deal with "non proxy hosts":
-     * since we do have to generate a RegexpPool we don't want to do that if
+     * since we do have to generate a pattern we don't want to do that if
      * it's not necessary. Therefore we do cache the result, on a per-protocol
      * basis, and change it only when the "source", i.e. the system property,
      * did change.
@@ -119,17 +120,17 @@
         static final String defStringVal = "localhost|127.*|[::1]|0.0.0.0|[::0]";
 
         String hostsSource;
-        RegexpPool hostsPool;
+        Pattern pattern;
         final String property;
         final String defaultVal;
         static NonProxyInfo ftpNonProxyInfo = new NonProxyInfo("ftp.nonProxyHosts", null, null, defStringVal);
         static NonProxyInfo httpNonProxyInfo = new NonProxyInfo("http.nonProxyHosts", null, null, defStringVal);
         static NonProxyInfo socksNonProxyInfo = new NonProxyInfo("socksNonProxyHosts", null, null, defStringVal);
 
-        NonProxyInfo(String p, String s, RegexpPool pool, String d) {
+        NonProxyInfo(String p, String s, Pattern pattern, String d) {
             property = p;
             hostsSource = s;
-            hostsPool = pool;
+            this.pattern = pattern;
             defaultVal = d;
         }
     }
@@ -255,7 +256,7 @@
                                             nphosts = nprop.defaultVal;
                                         } else {
                                             nprop.hostsSource = null;
-                                            nprop.hostsPool = null;
+                                            nprop.pattern = null;
                                         }
                                     } else if (nphosts.length() != 0) {
                                         // add the required default patterns
@@ -266,20 +267,11 @@
                                     }
                                     if (nphosts != null) {
                                         if (!nphosts.equals(nprop.hostsSource)) {
-                                            RegexpPool pool = new RegexpPool();
-                                            StringTokenizer st = new StringTokenizer(nphosts, "|", false);
-                                            try {
-                                                while (st.hasMoreTokens()) {
-                                                    pool.add(st.nextToken().toLowerCase(), Boolean.TRUE);
-                                                }
-                                            } catch (sun.misc.REException ex) {
-                                            }
-                                            nprop.hostsPool = pool;
+                                            nprop.pattern = toPattern(nphosts);
                                             nprop.hostsSource = nphosts;
                                         }
                                     }
-                                    if (nprop.hostsPool != null &&
-                                        nprop.hostsPool.match(urlhost) != null) {
+                                    if (shouldNotUseProxyFor(nprop.pattern, urlhost)) {
                                         return Proxy.NO_PROXY;
                                     }
                                 }
@@ -355,4 +347,49 @@
 
     private native static boolean init();
     private synchronized native Proxy getSystemProxy(String protocol, String host);
+
+    /**
+     * @return {@code true} if given this pattern for non-proxy hosts and this
+     *         urlhost the proxy should NOT be used to access this urlhost
+     */
+    static boolean shouldNotUseProxyFor(Pattern pattern, String urlhost) {
+        if (pattern == null || urlhost.isEmpty())
+            return false;
+        boolean matches = pattern.matcher(urlhost).matches();
+        return matches;
+    }
+
+    /**
+     * @param mask non-null mask
+     * @return {@link java.util.regex.Pattern} corresponding to this mask
+     *         or {@code null} in case mask should not match anything
+     */
+    static Pattern toPattern(String mask) {
+        boolean disjunctionEmpty = true;
+        StringJoiner joiner = new StringJoiner("|");
+        for (String disjunct : mask.split("\\|")) {
+            if (disjunct.isEmpty())
+                continue;
+            disjunctionEmpty = false;
+            String regex = disjunctToRegex(disjunct.toLowerCase());
+            joiner.add(regex);
+        }
+        return disjunctionEmpty ? null : Pattern.compile(joiner.toString());
+    }
+
+    /**
+     * @param disjunct non-null mask disjunct
+     * @return java regex string corresponding to this mask
+     */
+    static String disjunctToRegex(String disjunct) {
+        String regex;
+        if (disjunct.startsWith("*")) {
+            regex = ".*" + quote(disjunct.substring(1));
+        } else if (disjunct.endsWith("*")) {
+            regex = quote(disjunct.substring(0, disjunct.length() - 1)) + ".*";
+        } else {
+            regex = quote(disjunct);
+        }
+        return regex;
+    }
 }
diff --git a/test/java/net/ProxySelector/B8035158.java b/test/java/net/ProxySelector/B8035158.java
new file mode 100644
index 0000000..bc49892
--- /dev/null
+++ b/test/java/net/ProxySelector/B8035158.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/*
+ * @test
+ * @bug 8035158
+ * @run main/othervm B8035158
+ */
+
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.util.*;
+import java.util.concurrent.Callable;
+
+public class B8035158 {
+
+    public static void main(String[] args) {
+        for (TestCase t : emptyNonProxiesHosts()) t.run();
+        for (TestCase t : nonEmptyNonProxiesHosts()) t.run();
+        for (TestCase t : misc()) t.run();
+    }
+
+    // Setting http.nonProxyHosts to an empty string has an effect of
+    // not including default hosts to the list of exceptions
+    // (i.e. if you want everything to be connected directly rather than
+    // through proxy, you should set this property to an empty string)
+    private static Collection<TestCase> emptyNonProxiesHosts() {
+        List<TestCase> tests = new LinkedList<>();
+        String[] loopbacks = {"localhost", "[::1]", "[::0]", "0.0.0.0",
+                "127.0.0.0", "127.0.0.1", "127.0.1.0", "127.0.1.1",
+                "127.1.0.0", "127.1.0.1", "127.1.1.0", "127.1.1.1"};
+        Map<String, String> properties = new HashMap<>();
+        properties.put("http.proxyHost", "http://proxy.example.com");
+        properties.put("http.nonProxyHosts", "");
+        for (String s : loopbacks) {
+            tests.add(new TestCase(properties, "http://" + s, true));
+        }
+        return tests;
+    }
+
+    // No matter what is set into the http.nonProxyHosts (as far as it is not
+    // an empty string) loopback address aliases must be always connected
+    // directly
+    private static Collection<TestCase> nonEmptyNonProxiesHosts() {
+        List<TestCase> tests = new LinkedList<>();
+        String[] nonProxyHosts = {
+                "google.com",
+                "localhost", "[::1]", "[::0]", "0.0.0.0",
+                "127.0.0.0", "127.0.0.1", "127.0.1.0", "127.0.1.1",
+                "127.1.0.0", "127.1.0.1", "127.1.1.0", "127.1.1.1"};
+        String[] loopbacks = {"localhost", "[::1]", "[::0]", "0.0.0.0",
+                "127.0.0.0", "127.0.0.1", "127.0.1.0", "127.0.1.1",
+                "127.1.0.0", "127.1.0.1", "127.1.1.0", "127.1.1.1"};
+        for (String h : nonProxyHosts) {
+            for (String s : loopbacks) {
+                Map<String, String> properties = new HashMap<>();
+                properties.put("http.proxyHost", "http://proxy.example.com");
+                properties.put("http.nonProxyHosts", h);
+                tests.add(new TestCase(properties, "http://" + s, false));
+            }
+        }
+        return tests;
+    }
+
+    // unsorted tests
+    private static Collection<TestCase> misc() {
+        List<TestCase> t = new LinkedList<>();
+        t.add(new TestCase("oracle.com", "http://137.254.16.101", true));
+        t.add(new TestCase("google.com", "http://74.125.200.101", true));
+
+        t.add(new TestCase("google.com|google.ie", "http://google.co.uk",
+                true));
+        t.add(new TestCase("google.com|google.ie", "http://google.com",
+                false));
+        t.add(new TestCase("google.com|google.ie", "http://google.ie",
+                false));
+
+        t.add(new TestCase("google.com|bing.com|yahoo.com",
+                "http://127.0.0.1", false));
+        t.add(new TestCase("google.com|bing.com|yahoo.com",
+                "http://google.com", false));
+        t.add(new TestCase("google.com|bing.com|yahoo.com",
+                "http://bing.com", false));
+        t.add(new TestCase("google.com|bing.com|yahoo.com",
+                "http://yahoo.com", false));
+
+        t.add(new TestCase("google.com|bing.com", "http://google.com", false));
+        t.add(new TestCase("google.com|bing.com", "http://bing.com", false));
+        t.add(new TestCase("google.com|bing.com", "http://yahoo.com",
+                true));
+        t.add(new TestCase("google.com|bing.co*", "http://google.com", false));
+        t.add(new TestCase("google.com|bing.co*", "http://bing.com", false));
+        t.add(new TestCase("google.com|bing.co*", "http://yahoo.com",
+                true));
+        t.add(new TestCase("google.com|*ing.com", "http://google.com", false));
+        t.add(new TestCase("google.com|*ing.com", "http://bing.com", false));
+        t.add(new TestCase("google.com|*ing.com", "http://yahoo.com",
+                true));
+        t.add(new TestCase("google.co*|bing.com", "http://google.com", false));
+        t.add(new TestCase("google.co*|bing.com", "http://bing.com", false));
+        t.add(new TestCase("google.co*|bing.com", "http://yahoo.com",
+                true));
+        t.add(new TestCase("google.co*|bing.co*", "http://google.com", false));
+        t.add(new TestCase("google.co*|bing.co*", "http://bing.com", false));
+        t.add(new TestCase("google.co*|bing.co*", "http://yahoo.com",
+                true));
+        t.add(new TestCase("google.co*|*ing.com", "http://google.com", false));
+        t.add(new TestCase("google.co*|*ing.com", "http://bing.com", false));
+        t.add(new TestCase("google.co*|*ing.com", "http://yahoo.com",
+                true));
+        t.add(new TestCase("*oogle.com|bing.com", "http://google.com", false));
+        t.add(new TestCase("*oogle.com|bing.com", "http://bing.com", false));
+        t.add(new TestCase("*oogle.com|bing.com", "http://yahoo.com",
+                true));
+        t.add(new TestCase("*oogle.com|bing.co*", "http://google.com", false));
+        t.add(new TestCase("*oogle.com|bing.co*", "http://bing.com", false));
+        t.add(new TestCase("*oogle.com|bing.co*", "http://yahoo.com",
+                true));
+        t.add(new TestCase("*oogle.com|*ing.com", "http://google.com", false));
+        t.add(new TestCase("*oogle.com|*ing.com", "http://bing.com", false));
+        t.add(new TestCase("*oogle.com|*ing.com", "http://yahoo.com",
+                true));
+
+        t.add(new TestCase("google.com|bing.com|yahoo.com", "http://google.com", false));
+        t.add(new TestCase("google.com|bing.com|yahoo.com", "http://bing.com", false));
+        t.add(new TestCase("google.com|bing.com|yahoo.com", "http://yahoo.com", false));
+        t.add(new TestCase("google.com|bing.com|yahoo.com",
+                "http://duckduckgo.com", true));
+
+        t.add(new TestCase("p-proxy.com", "http://p-proxy.com", false));
+        t.add(new TestCase("google.co*|google.ie", "http://google.co.uk",
+                false));
+
+        t.add(new TestCase("*oracle.com", "http://my.oracle.com", false));
+        t.add(new TestCase("google.com|bing.com|yahoo.com", "http://127.0.0.1", false));
+        t.add(new TestCase("google.com|bing.com|yahoo.com", "http://yahoo.com", false));
+
+        // example from
+        // http://docs.oracle.com/javase/7/docs/technotes/guides/net/proxies.html
+        t.add(new TestCase("localhost|host.example.com", "http://localhost",
+                false));
+        t.add(new TestCase("localhost|host.example.com",
+                "http://host.example.com", false));
+        t.add(new TestCase("localhost|host.example.com",
+                "http://google.com", true));
+        return t;
+    }
+
+
+    private static <T> T withSystemPropertiesSet(
+            Map<String, String> localProperties,
+            Callable<? extends T> code) {
+        Map<String, String> backup = new HashMap<>();
+        try {
+            backupAndSetProperties(localProperties, backup);
+            return code.call();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+            restoreProperties(backup);
+        }
+    }
+
+    private static void backupAndSetProperties(
+            Map<String, String> localProperties,
+            Map<String, String> oldProperties) {
+        for (Map.Entry<String, String> e : localProperties.entrySet()) {
+            String oldValue = System.setProperty(e.getKey(), e.getValue());
+            oldProperties.put(e.getKey(), oldValue);
+        }
+    }
+
+    private static void restoreProperties(Map<String, String> oldProperties) {
+        for (Map.Entry<String, String> e : oldProperties.entrySet()) {
+            String oldValue = e.getValue();
+            String key = e.getKey();
+            if (oldValue == null)
+                System.getProperties().remove(key);
+            else
+                System.setProperty(key, oldValue);
+        }
+    }
+
+    private static class TestCase {
+
+        final Map<String, String> localProperties;
+        final String urlhost;
+        final boolean expectedProxying;
+
+        TestCase(String nonProxyHosts, String urlhost,
+                 boolean expectedProxying) {
+            this(nonProxyHosts, "proxy.example.com", urlhost,
+                    expectedProxying);
+        }
+
+        TestCase(String nonProxyHosts, String proxyHost, String urlhost,
+                 boolean expectedProxying) {
+            this(new HashMap<String, String>() {
+                {
+                    put("http.nonProxyHosts", nonProxyHosts);
+                    put("http.proxyHost", proxyHost);
+                }
+            }, urlhost, expectedProxying);
+        }
+
+        TestCase(Map<String, String> localProperties, String urlhost,
+                 boolean expectedProxying) {
+            this.localProperties = localProperties;
+            this.urlhost = urlhost;
+            this.expectedProxying = expectedProxying;
+        }
+
+        void run() {
+            System.out.printf("urlhost=%s properties=%s: proxied? %s%n",
+                    urlhost, localProperties, expectedProxying);
+
+            List<Proxy> proxies = withSystemPropertiesSet(localProperties,
+                    () -> ProxySelector.getDefault().select(
+                            URI.create(urlhost))
+            );
+
+            verify(proxies);
+        }
+
+        void verify(List<? extends Proxy> proxies) {
+
+            boolean actualProxying = !(proxies.size() == 1 &&
+                    proxies.get(0).type() == Proxy.Type.DIRECT);
+
+            if (actualProxying != expectedProxying)
+                throw new AssertionError(String.format(
+                        "Expected %s connection for %s (given " +
+                                "properties=%s). Here's the list of proxies " +
+                                "returned: %s",
+                        expectedProxying ? "proxied" : "direct", urlhost,
+                        localProperties, proxies
+                ));
+        }
+    }
+}