This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 3b6a6f3bd27f526e4c994154abbb446d8931c22e
Author: Claus Ibsen <claus.ib...@gmail.com>
AuthorDate: Sun Feb 23 19:43:30 2020 +0100

    CAMEL-14609: camel-core - optimize
---
 .../java/org/apache/camel/util/StringHelper.java   |   5 +-
 .../java/org/apache/camel/util/URIScanner.java     | 108 +++++++++++----------
 .../java/org/apache/camel/util/URISupport.java     |  80 ++++++++++-----
 .../camel/util/UnsafeUriCharactersEncoder.java     |  55 +++++------
 .../apache/camel/itest/jmh/NormalizeUriTest.java   | 100 +++++++++++++++++++
 5 files changed, 242 insertions(+), 106 deletions(-)

diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java 
b/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java
index f5e2669..d955e58 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java
@@ -73,12 +73,13 @@ public final class StringHelper {
      * @return number of times char is located in the string
      */
     public static int countChar(String s, char ch) {
-        if (ObjectHelper.isEmpty(s)) {
+        if (s == null || s.isEmpty()) {
             return 0;
         }
 
         int matches = 0;
-        for (int i = 0; i < s.length(); i++) {
+        int len = s.length();
+        for (int i = 0; i < len; i++) {
             char c = s.charAt(i);
             if (ch == c) {
                 matches++;
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/URIScanner.java 
b/core/camel-util/src/main/java/org/apache/camel/util/URIScanner.java
index 2ad8f5f..014065b 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/URIScanner.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/URIScanner.java
@@ -37,41 +37,31 @@ class URIScanner {
     private static final String RAW_START_ONE = RAW_TOKEN_PREFIX + 
RAW_TOKEN_START[0];
     private static final String RAW_START_TWO = RAW_TOKEN_PREFIX + 
RAW_TOKEN_START[1];
 
+    // TODO: when upgrading to JDK11 as minimum then use java.nio.Charset
+    private static final String CHARSET = "UTF-8";
+
     private enum Mode {
         KEY, VALUE
     }
 
     private static final char END = '\u0000';
 
-    private final String charset;
     private final StringBuilder key;
     private final StringBuilder value;
     private Mode mode;
     private boolean isRaw;
     private char rawTokenEnd;
 
-    public URIScanner(String charset) {
-        this.charset = charset;
-        key = new StringBuilder();
-        value = new StringBuilder();
+    public URIScanner() {
+        this.key = new StringBuilder();
+        this.value = new StringBuilder();
     }
 
     private void initState() {
-        mode = Mode.KEY;
-        key.setLength(0);
-        value.setLength(0);
-        isRaw = false;
-    }
-
-    private String getDecodedKey() throws UnsupportedEncodingException {
-        return URLDecoder.decode(key.toString(), charset);
-    }
-
-    private String getDecodedValue() throws UnsupportedEncodingException {
-        // need to replace % with %25
-        String s = StringHelper.replaceAll(value.toString(), "%", "%25");
-        String answer = URLDecoder.decode(s, charset);
-        return answer;
+        this.mode = Mode.KEY;
+        this.key.setLength(0);
+        this.value.setLength(0);
+        this.isRaw = false;
     }
 
     public Map<String, Object> parseQuery(String uri, boolean useRaw) throws 
URISyntaxException {
@@ -85,12 +75,13 @@ class URIScanner {
             initState();
 
             // parse the uri parameters char by char
-            for (int i = 0; i < uri.length(); i++) {
+            int len = uri.length();
+            for (int i = 0; i < len; i++) {
                 // current char
                 char ch = uri.charAt(i);
                 // look ahead of the next char
                 char next;
-                if (i <= uri.length() - 2) {
+                if (i <= len - 2) {
                     next = uri.charAt(i + 1);
                 } else {
                     next = END;
@@ -165,13 +156,19 @@ class URIScanner {
             return false;
         }
 
-        String start = value.substring(0, 4);
-        if (start.startsWith(RAW_START_ONE)) {
-            rawTokenEnd = RAW_TOKEN_END[0];
-            return true;
-        } else if (start.startsWith(RAW_START_TWO)) {
-            rawTokenEnd = RAW_TOKEN_END[1];
-            return true;
+        // optimize to not create new objects
+        char char1 = value.charAt(0);
+        char char2 = value.charAt(1);
+        char char3 = value.charAt(2);
+        char char4 = value.charAt(3);
+        if (char1 == 'R' && char2 == 'A' && char3 == 'W') {
+            if (char4 == '(') {
+                rawTokenEnd = RAW_TOKEN_END[0];
+                return true;
+            } else if (char4 == '{') {
+                rawTokenEnd = RAW_TOKEN_END[1];
+                return true;
+            }
         }
 
         return false;
@@ -183,8 +180,14 @@ class URIScanner {
     }
 
     private void addParameter(Map<String, Object> answer, boolean isRaw) 
throws UnsupportedEncodingException {
-        String name = getDecodedKey();
-        String value = isRaw ? this.value.toString() : getDecodedValue();
+        String name = URLDecoder.decode(key.toString(), CHARSET);
+        String text;
+        if (isRaw) {
+            text = value.toString();
+        } else {
+            String s = StringHelper.replaceAll(value.toString(), "%", "%25");
+            text = URLDecoder.decode(s, CHARSET);
+        }
 
         // does the key already exist?
         if (answer.containsKey(name)) {
@@ -202,13 +205,14 @@ class URIScanner {
                     list.add(s);
                 }
             }
-            list.add(value);
+            list.add(text);
             answer.put(name, list);
         } else {
-            answer.put(name, value);
+            answer.put(name, text);
         }
     }
 
+    @SuppressWarnings("unchecked")
     public static List<Pair<Integer>> scanRaw(String str) {
         if (str == null || ObjectHelper.isEmpty(str)) {
             return Collections.EMPTY_LIST;
@@ -224,7 +228,6 @@ class URIScanner {
                 char tokenEnd = RAW_TOKEN_END[i];
                 if (str.startsWith(tokenStart, start)) {
                     offset = scanRawToEnd(str, start, tokenStart, tokenEnd, 
answer);
-                    continue;
                 }
             }
             start = str.indexOf(RAW_TOKEN_PREFIX, offset);
@@ -248,29 +251,32 @@ class URIScanner {
         return end + 1;
     }
 
-    public static boolean isRaw(int index, List<Pair<Integer>> pairs) {
-        for (Pair<Integer> pair : pairs) {
-            if (index < pair.getLeft()) {
-                return false;
-            }
-            if (index <= pair.getRight()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     public static String resolveRaw(String str) {
         int len = str.length();
         if (len <= 4) {
             return null;
         }
-        int last = len - 1;
 
-        // check for end is quicker than for start
-        if (str.charAt(last) == ')' && str.startsWith(RAW_START_ONE)
-                || str.charAt(last) == '}' && str.startsWith(RAW_START_TWO)) {
-            return str.substring(4, last);
+        int endPos = len - 1;
+        char last = str.charAt(endPos);
+
+        // optimize to not create new objects
+        if (last == ')') {
+            char char1 = str.charAt(0);
+            char char2 = str.charAt(1);
+            char char3 = str.charAt(2);
+            char char4 = str.charAt(3);
+            if (char1 == 'R' && char2 == 'A' && char3 == 'W' && char4 == '(') {
+                return str.substring(4, endPos);
+            }
+        } else if (last == '}') {
+            char char1 = str.charAt(0);
+            char char2 = str.charAt(1);
+            char char3 = str.charAt(2);
+            char char4 = str.charAt(3);
+            if (char1 == 'R' && char2 == 'A' && char3 == 'W' && char4 == '{') {
+                return str.substring(4, endPos);
+            }
         }
 
         // not RAW value
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java 
b/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
index a4798de..6b0eb19 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
@@ -21,10 +21,12 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URLEncoder;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.regex.Pattern;
 
 /**
@@ -184,7 +186,7 @@ public final class URISupport {
             throw new URISyntaxException(uri, "Invalid uri syntax: Trailing & 
marker found. " + "Check the uri and remove the trailing & marker.");
         }
 
-        URIScanner scanner = new URIScanner(CHARSET);
+        URIScanner scanner = new URIScanner();
         return scanner.parseQuery(uri, useRaw);
     }
 
@@ -226,7 +228,19 @@ public final class URISupport {
      * @see #RAW_TOKEN_END
      */
     public static boolean isRaw(int index, List<Pair<Integer>> pairs) {
-        return URIScanner.isRaw(index, pairs);
+        if (pairs == null || pairs.isEmpty()) {
+            return false;
+        }
+
+        for (Pair<Integer> pair : pairs) {
+            if (index < pair.getLeft()) {
+                return false;
+            }
+            if (index <= pair.getRight()) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -237,22 +251,33 @@ public final class URISupport {
      * @throws URISyntaxException is thrown if uri has invalid syntax.
      */
     public static Map<String, Object> parseParameters(URI uri) throws 
URISyntaxException {
+        String query = prepareQuery(uri);
+        if (query == null) {
+            // empty an empty map
+            return new LinkedHashMap<>(0);
+        }
+        return parseQuery(query);
+    }
+
+    public static String prepareQuery(URI uri) {
         String query = uri.getQuery();
         if (query == null) {
             String schemeSpecificPart = uri.getSchemeSpecificPart();
             int idx = schemeSpecificPart.indexOf('?');
             if (idx < 0) {
-                // return an empty map
-                return new LinkedHashMap<>(0);
+                return null;
             } else {
                 query = schemeSpecificPart.substring(idx + 1);
             }
-        } else {
-            query = stripPrefix(query, "?");
+        } else if (query.indexOf('?') == 0) {
+            // skip leading query
+            query = query.substring(1);
         }
-        return parseQuery(query);
+        return query;
     }
 
+
+
     /**
      * Traverses the given parameters, and resolve any parameter values which
      * uses the RAW token syntax: <tt>key=RAW(value)</tt>. This method will 
then
@@ -380,11 +405,15 @@ public final class URISupport {
      */
     @SuppressWarnings("unchecked")
     public static String createQueryString(Map<String, Object> options) throws 
URISyntaxException {
+        return createQueryString(options.keySet(), options);
+    }
+
+    public static String createQueryString(Collection<String> sortedKeys, 
Map<String, Object> options) throws URISyntaxException {
         try {
             if (options.size() > 0) {
                 StringBuilder rc = new StringBuilder();
                 boolean first = true;
-                for (Object o : options.keySet()) {
+                for (Object o : sortedKeys) {
                     if (first) {
                         first = false;
                     } else {
@@ -495,8 +524,8 @@ public final class URISupport {
     public static String normalizeUri(String uri) throws URISyntaxException, 
UnsupportedEncodingException {
 
         URI u = new URI(UnsafeUriCharactersEncoder.encode(uri, true));
-        String path = u.getSchemeSpecificPart();
         String scheme = u.getScheme();
+        String path = u.getSchemeSpecificPart();
 
         // not possible to normalize
         if (scheme == null || path == null) {
@@ -513,7 +542,7 @@ public final class URISupport {
             path = path.substring(0, idx);
         }
 
-        if (u.getScheme().startsWith("http")) {
+        if (scheme.startsWith("http")) {
             path = UnsafeUriCharactersEncoder.encodeHttpURI(path);
         } else {
             path = UnsafeUriCharactersEncoder.encode(path);
@@ -528,8 +557,9 @@ public final class URISupport {
         // this to work out of the box with Camel, and hence we need to fix it
         // for them
         String userInfoPath = path;
-        if (userInfoPath.contains("/")) {
-            userInfoPath = userInfoPath.substring(0, 
userInfoPath.indexOf("/"));
+        idx = userInfoPath.indexOf('/');
+        if (idx != -1) {
+            userInfoPath = userInfoPath.substring(0, idx);
         }
         if (StringHelper.countChar(userInfoPath, '@') > 1) {
             int max = userInfoPath.lastIndexOf('@');
@@ -543,23 +573,25 @@ public final class URISupport {
         }
 
         // in case there are parameters we should reorder them
-        Map<String, Object> parameters = URISupport.parseParameters(u);
-        if (parameters.isEmpty()) {
+        String query = prepareQuery(u);
+        if (query == null) {
             // no parameters then just return
             return buildUri(scheme, path, null);
         } else {
-            // reorder parameters a..z
-            List<String> keys = new ArrayList<>(parameters.keySet());
-            keys.sort(null);
+            Map<String, Object> parameters = URISupport.parseQuery(query, 
false, false);
+            if (parameters.size() == 1) {
+                // only 1 parameter need to create new query string
+                query = URISupport.createQueryString(parameters);
+                return buildUri(scheme, path, query);
+            } else {
+                // reorder parameters a..z
+                List<String> keys = new ArrayList<>(parameters.keySet());
+                keys.sort(null);
 
-            Map<String, Object> sorted = new 
LinkedHashMap<>(parameters.size());
-            for (String key : keys) {
-                sorted.put(key, parameters.get(key));
+                // build uri object with sorted parameters
+                query = URISupport.createQueryString(keys, parameters);
+                return buildUri(scheme, path, query);
             }
-
-            // build uri object with sorted parameters
-            String query = URISupport.createQueryString(sorted);
-            return buildUri(scheme, path, query);
         }
     }
 
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/UnsafeUriCharactersEncoder.java
 
b/core/camel-util/src/main/java/org/apache/camel/util/UnsafeUriCharactersEncoder.java
index d5030f2..d02de20 100644
--- 
a/core/camel-util/src/main/java/org/apache/camel/util/UnsafeUriCharactersEncoder.java
+++ 
b/core/camel-util/src/main/java/org/apache/camel/util/UnsafeUriCharactersEncoder.java
@@ -16,7 +16,6 @@
  */
 package org.apache.camel.util;
 
-import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.List;
 
@@ -93,42 +92,44 @@ public final class UnsafeUriCharactersEncoder {
 
     // Just skip the encode for isRAW part
     public static String encode(String s, BitSet unsafeCharacters, boolean 
checkRaw) {
-        List<Pair<Integer>> rawPairs;
-        if (checkRaw) {
-            rawPairs = URISupport.scanRaw(s);
-        } else {
-            rawPairs = new ArrayList<>();
-        }
-   
-        int n = s == null ? 0 : s.length();
-        if (n == 0) {
+        if (s == null || s.length() == 0) {
             return s;
         }
 
-        // First check whether we actually need to encode
+        // first check whether we actually need to encode
         char[] chars = s.toCharArray();
-        for (int i = 0;;) {
+        int len = chars.length;
+        boolean safe = true;
+        for (char ch : chars) {
             // just deal with the ascii character
-            if (chars[i] > 0 && chars[i] < 128) {
-                if (unsafeCharacters.get(chars[i])) {
-                    break;
-                }
-            }
-            if (++i >= chars.length) {
-                return s;
+            if (ch > 0 && ch < 128 && unsafeCharacters.get(ch)) {
+                safe = false;
+                break;
             }
         }
+        if (safe) {
+            return s;
+        }
+
+        List<Pair<Integer>> rawPairs = null;
+        if (checkRaw) {
+            rawPairs = URISupport.scanRaw(s);
+        }
+
+
+        // add a bit of extra space as initial capacity
+        int initial = len + 8;
 
         // okay there are some unsafe characters so we do need to encode
         // see details at: http://en.wikipedia.org/wiki/Url_encode
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < chars.length; i++) {
+        StringBuilder sb = new StringBuilder(initial);
+        for (int i = 0; i < len; i++) {
             char ch = chars[i];
             if (ch > 0 && ch < 128 && unsafeCharacters.get(ch)) {
                 // special for % sign as it may be a decimal encoded value
                 if (ch == '%') {
-                    char next = i + 1 < chars.length ? chars[i + 1] : ' ';
-                    char next2 = i + 2 < chars.length ? chars[i + 2] : ' ';
+                    char next = i + 1 < len ? chars[i + 1] : ' ';
+                    char next2 = i + 2 < len ? chars[i + 2] : ' ';
 
                     if (isHexDigit(next) && isHexDigit(next2) && 
!URISupport.isRaw(i, rawPairs)) {
                         // its already encoded (decimal encoded) so just 
append as is
@@ -155,12 +156,8 @@ public final class UnsafeUriCharactersEncoder {
     }
 
     private static boolean isHexDigit(char ch) {
-        for (char hex : HEX_DIGITS) {
-            if (hex == ch) {
-                return true;
-            }
-        }
-        return false;
+        // 0..9 A..F a..f
+        return ch >= 48 && ch <= 57 || ch >= 65 && ch <= 70 || ch >= 97 && ch 
<= 102;
     }
 
 }
diff --git 
a/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/NormalizeUriTest.java
 
b/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/NormalizeUriTest.java
new file mode 100644
index 0000000..8f9a6e2
--- /dev/null
+++ 
b/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/NormalizeUriTest.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.itest.jmh;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.util.URISupport;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.infra.Blackhole;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+
+/**
+ * Tests the {@link org.apache.camel.util.URISupport#normalizeUri(String)}.
+ * <p/>
+ * Thanks to this SO answer: 
https://stackoverflow.com/questions/30485856/how-to-run-jmh-from-inside-junit-tests
+ */
+public class NormalizeUriTest {
+
+    @Test
+    public void launchBenchmark() throws Exception {
+        Options opt = new OptionsBuilder()
+            // Specify which benchmarks to run.
+            // You can be more specific if you'd like to run only one 
benchmark per test.
+            .include(this.getClass().getName() + ".*")
+            // Set the following options as needed
+            .mode(Mode.All)
+            .timeUnit(TimeUnit.MICROSECONDS)
+            .warmupTime(TimeValue.seconds(1))
+            .warmupIterations(2)
+            .measurementTime(TimeValue.seconds(1))
+            .measurementIterations(2)
+            .threads(2)
+            .forks(1)
+            .shouldFailOnError(true)
+            .shouldDoGC(false)
+            .measurementBatchSize(100000)
+            .build();
+
+        new Runner(opt).run();
+    }
+
+    // The JMH samples are the best documentation for how to use it
+    // 
http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/
+    @State(Scope.Thread)
+    public static class BenchmarkState {
+        @Setup(Level.Trial)
+        public void initialize() {
+        }
+    }
+
+    @Benchmark
+    public void benchmark(ContainsIgnoreCaseTest.BenchmarkState state, 
Blackhole bh) throws Exception {
+        bh.consume(URISupport.normalizeUri("log:foo"));
+        
bh.consume(URISupport.normalizeUri("log:foo?level=INFO&logMask=false&exchangeFormatter=#myFormatter"));
+        
bh.consume(URISupport.normalizeUri("smtp://localhost?password=secret&username=davsclaus"));
+        bh.consume(URISupport.normalizeUri("seda:foo?concurrentConsumer=2"));
+        
bh.consume(URISupport.normalizeUri("irc:someserver/#camel?user=davsclaus"));
+        bh.consume(URISupport.normalizeUri("http:www.google.com?q=Camel"));
+        
bh.consume(URISupport.normalizeUri("http://www.google.com?q=S%C3%B8ren%20Hansen";));
+        
bh.consume(URISupport.normalizeUri("smtp://localhost?to=foo&to=bar&from=me&from=you"));
+        
bh.consume(URISupport.normalizeUri("ftp://us%40r:t%st@localhost:21000/tmp3/camel?foo=us@r";));
+        
bh.consume(URISupport.normalizeUri("ftp://us%40r:t%25st@localhost:21000/tmp3/camel?foo=us@r";));
+        
bh.consume(URISupport.normalizeUri("ftp://us@r:t%st@localhost:21000/tmp3/camel?foo=us@r";));
+        
bh.consume(URISupport.normalizeUri("ftp://us@r:t%25st@localhost:21000/tmp3/camel?foo=us@r";));
+        
bh.consume(URISupport.normalizeUri("xmpp://camel-user@localhost:123/test-user@localhost?password=secret&serviceName=someCoolChat"));
+        
bh.consume(URISupport.normalizeUri("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW(++?w0rd)&serviceName=some
 chat"));
+        
bh.consume(URISupport.normalizeUri("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW(foo
 %% bar)&serviceName=some chat"));
+        
bh.consume(URISupport.normalizeUri("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW{++?w0rd}&serviceName=some
 chat"));
+        
bh.consume(URISupport.normalizeUri("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW{foo
 %% bar}&serviceName=some chat"));
+    }
+
+    @Benchmark
+    public void sorted(ContainsIgnoreCaseTest.BenchmarkState state, Blackhole 
bh) throws Exception {
+        
bh.consume(URISupport.normalizeUri("log:foo?zzz=123&xxx=222&hhh=444&aaa=tru&d=yes&cc=no&Camel=awesome&foo.hey=bar&foo.bar=blah"));
+    }
+
+}

Reply via email to