Repository: camel Updated Branches: refs/heads/master 95ebcb4fe -> d97c96251
CAMEL-11420-Add contains ignore case operator to simple language CAMEL-11420-add jmh test CAMEL-11420 - null check added for containsIgnoreCase in StringHelper CAMEL-11420 - CR fixes CAMEL-11420- update simple-language.adoc for contains ignore case Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/d97c9625 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/d97c9625 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/d97c9625 Branch: refs/heads/master Commit: d97c96251a21b2d4b525401136ec30502b5dc79a Parents: 95ebcb4 Author: onders86 <[email protected]> Authored: Mon Jun 19 20:06:38 2017 +0300 Committer: onders86 <[email protected]> Committed: Thu Jun 22 15:07:47 2017 +0300 ---------------------------------------------------------------------- .../src/main/resources/camel-checkstyle.xml | 2 +- camel-core/src/main/docs/simple-language.adoc | 2 + .../apache/camel/builder/PredicateBuilder.java | 21 +++++ .../camel/language/simple/SimpleTokenizer.java | 1 + .../language/simple/ast/BinaryExpression.java | 2 + .../simple/types/BinaryOperatorType.java | 9 ++- .../org/apache/camel/util/ObjectHelper.java | 37 +++++++++ .../org/apache/camel/util/StringHelper.java | 35 ++++++++ .../language/simple/SimpleOperatorTest.java | 7 ++ .../camel/itest/jmh/ContainsIgnoreCaseTest.java | 85 ++++++++++++++++++++ 10 files changed, 199 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/d97c9625/buildingtools/src/main/resources/camel-checkstyle.xml ---------------------------------------------------------------------- diff --git a/buildingtools/src/main/resources/camel-checkstyle.xml b/buildingtools/src/main/resources/camel-checkstyle.xml index 44e7c10..3b84c67 100644 --- a/buildingtools/src/main/resources/camel-checkstyle.xml +++ b/buildingtools/src/main/resources/camel-checkstyle.xml @@ -264,7 +264,7 @@ lengths, if/try depths, etc... <module name="JUnitTestCase"/> --> <module name="ReturnCount"> - <property name="max" value="20"/> + <property name="max" value="21"/> <property name="maxForVoid" value="25"/> </module> http://git-wip-us.apache.org/repos/asf/camel/blob/d97c9625/camel-core/src/main/docs/simple-language.adoc ---------------------------------------------------------------------- diff --git a/camel-core/src/main/docs/simple-language.adoc b/camel-core/src/main/docs/simple-language.adoc index f13ed33..c8e428c 100644 --- a/camel-core/src/main/docs/simple-language.adoc +++ b/camel-core/src/main/docs/simple-language.adoc @@ -469,6 +469,8 @@ values) |not contains |For testing if not contains in a string based value +|~~ |For testing if contains by ignoring case sensitivity in a string based value + |regex |For matching against a given regular expression pattern defined as a String value http://git-wip-us.apache.org/repos/asf/camel/blob/d97c9625/camel-core/src/main/java/org/apache/camel/builder/PredicateBuilder.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/builder/PredicateBuilder.java b/camel-core/src/main/java/org/apache/camel/builder/PredicateBuilder.java index 05c5673..b0511f8 100644 --- a/camel-core/src/main/java/org/apache/camel/builder/PredicateBuilder.java +++ b/camel-core/src/main/java/org/apache/camel/builder/PredicateBuilder.java @@ -330,6 +330,27 @@ public final class PredicateBuilder { } }; } + + public static Predicate containsIgnoreCase(final Expression left, final Expression right) { + return new BinaryPredicateSupport(left, right) { + + protected boolean matches(Exchange exchange, Object leftValue, Object rightValue) { + if (leftValue == null && rightValue == null) { + // they are equal + return true; + } else if (leftValue == null || rightValue == null) { + // only one of them is null so they are not equal + return false; + } + + return ObjectHelper.containsIgnoreCase(leftValue, rightValue); + } + + protected String getOperationText() { + return "~~"; + } + }; + } public static Predicate isNull(final Expression expression) { return new BinaryPredicateSupport(expression, ExpressionBuilder.constantExpression(null)) { http://git-wip-us.apache.org/repos/asf/camel/blob/d97c9625/camel-core/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java b/camel-core/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java index 4718cbe..b72f23d 100644 --- a/camel-core/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java +++ b/camel-core/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java @@ -63,6 +63,7 @@ public final class SimpleTokenizer { KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "is")); KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "not contains")); KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "contains")); + KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "~~")); KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "not regex")); KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "regex")); KNOWN_TOKENS.add(new SimpleTokenType(TokenType.binaryOperator, "not in")); http://git-wip-us.apache.org/repos/asf/camel/blob/d97c9625/camel-core/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java b/camel-core/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java index 4b6e821..e46b8c9 100644 --- a/camel-core/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java +++ b/camel-core/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java @@ -96,6 +96,8 @@ public class BinaryExpression extends BaseSimpleNode { return createExpression(leftExp, rightExp, PredicateBuilder.contains(leftExp, rightExp)); } else if (operator == BinaryOperatorType.NOT_CONTAINS) { return createExpression(leftExp, rightExp, PredicateBuilder.not(PredicateBuilder.contains(leftExp, rightExp))); + } else if (operator == BinaryOperatorType.CONTAINS_IGNORECASE) { + return createExpression(leftExp, rightExp, PredicateBuilder.containsIgnoreCase(leftExp, rightExp)); } else if (operator == BinaryOperatorType.IS || operator == BinaryOperatorType.NOT_IS) { return createIsExpression(expression, leftExp, rightExp); } else if (operator == BinaryOperatorType.REGEX || operator == BinaryOperatorType.NOT_REGEX) { http://git-wip-us.apache.org/repos/asf/camel/blob/d97c9625/camel-core/src/main/java/org/apache/camel/language/simple/types/BinaryOperatorType.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/language/simple/types/BinaryOperatorType.java b/camel-core/src/main/java/org/apache/camel/language/simple/types/BinaryOperatorType.java index 5a7751b..5ee1877 100644 --- a/camel-core/src/main/java/org/apache/camel/language/simple/types/BinaryOperatorType.java +++ b/camel-core/src/main/java/org/apache/camel/language/simple/types/BinaryOperatorType.java @@ -21,7 +21,8 @@ package org.apache.camel.language.simple.types; */ public enum BinaryOperatorType { - EQ, EQ_IGNORE, GT, GTE, LT, LTE, NOT_EQ, CONTAINS, NOT_CONTAINS, REGEX, NOT_REGEX, + EQ, EQ_IGNORE, GT, GTE, LT, LTE, NOT_EQ, CONTAINS, NOT_CONTAINS, + CONTAINS_IGNORECASE, REGEX, NOT_REGEX, IN, NOT_IN, IS, NOT_IS, RANGE, NOT_RANGE, STARTS_WITH, ENDS_WITH; public static BinaryOperatorType asOperator(String text) { @@ -43,6 +44,8 @@ public enum BinaryOperatorType { return CONTAINS; } else if ("not contains".equals(text)) { return NOT_CONTAINS; + } else if ("~~".equals(text)) { + return CONTAINS_IGNORECASE; } else if ("regex".equals(text)) { return REGEX; } else if ("not regex".equals(text)) { @@ -86,6 +89,8 @@ public enum BinaryOperatorType { return "contains"; } else if (operator == NOT_CONTAINS) { return "not contains"; + } else if (operator == CONTAINS_IGNORECASE) { + return "~~"; } else if (operator == REGEX) { return "regex"; } else if (operator == NOT_REGEX) { @@ -174,6 +179,8 @@ public enum BinaryOperatorType { return null; } else if (operator == NOT_CONTAINS) { return null; + } else if (operator == CONTAINS_IGNORECASE) { + return null; } else if (operator == REGEX) { return new ParameterType[]{ParameterType.Literal, ParameterType.Function}; } else if (operator == NOT_REGEX) { http://git-wip-us.apache.org/repos/asf/camel/blob/d97c9625/camel-core/src/main/java/org/apache/camel/util/ObjectHelper.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/util/ObjectHelper.java b/camel-core/src/main/java/org/apache/camel/util/ObjectHelper.java index 829bcaf..73b6fae 100644 --- a/camel-core/src/main/java/org/apache/camel/util/ObjectHelper.java +++ b/camel-core/src/main/java/org/apache/camel/util/ObjectHelper.java @@ -195,6 +195,13 @@ public final class ObjectHelper { public static boolean equal(Object a, Object b) { return equal(a, b, false); } + + /** + * A helper method for comparing objects for equality while handling case insensitivity + */ + public static boolean equalIgnoreCase(Object a, Object b) { + return equal(a, b, true); + } /** * A helper method for comparing objects for equality while handling nulls @@ -647,6 +654,36 @@ public final class ObjectHelper { } return false; } + + /** + * Returns true if the collection contains the specified value by considering case insensitivity + */ + public static boolean containsIgnoreCase(Object collectionOrArray, Object value) { + // favor String types + if (collectionOrArray != null && (collectionOrArray instanceof StringBuffer || collectionOrArray instanceof StringBuilder)) { + collectionOrArray = collectionOrArray.toString(); + } + if (value != null && (value instanceof StringBuffer || value instanceof StringBuilder)) { + value = value.toString(); + } + + if (collectionOrArray instanceof Collection) { + Collection<?> collection = (Collection<?>)collectionOrArray; + return collection.contains(value); + } else if (collectionOrArray instanceof String && value instanceof String) { + String str = (String)collectionOrArray; + String subStr = (String)value; + return StringHelper.containsIgnoreCase(str, subStr); + } else { + Iterator<Object> iter = createIterator(collectionOrArray); + while (iter.hasNext()) { + if (equalIgnoreCase(value, iter.next())) { + return true; + } + } + } + return false; + } /** * Creates an iterable over the value if the value is a collection, an http://git-wip-us.apache.org/repos/asf/camel/blob/d97c9625/camel-core/src/main/java/org/apache/camel/util/StringHelper.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/util/StringHelper.java b/camel-core/src/main/java/org/apache/camel/util/StringHelper.java index 635c07f..a9ce41f 100644 --- a/camel-core/src/main/java/org/apache/camel/util/StringHelper.java +++ b/camel-core/src/main/java/org/apache/camel/util/StringHelper.java @@ -672,5 +672,40 @@ public final class StringHelper { return trimmed; } + + /** + * Checks if the src string contains what + * + * @param src is the source string to be checked + * @param what is the string which will be looked up in the src argument + * @return true/false + */ + public static boolean containsIgnoreCase(String src, String what) { + if (src == null || what == null) { + return false; + } + + final int length = what.length(); + if (length == 0) { + return true; // Empty string is contained + } + + final char firstLo = Character.toLowerCase(what.charAt(0)); + final char firstUp = Character.toUpperCase(what.charAt(0)); + + for (int i = src.length() - length; i >= 0; i--) { + // Quick check before calling the more expensive regionMatches() method: + final char ch = src.charAt(i); + if (ch != firstLo && ch != firstUp) { + continue; + } + + if (src.regionMatches(true, i, what, 0, length)) { + return true; + } + } + + return false; + } } http://git-wip-us.apache.org/repos/asf/camel/blob/d97c9625/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java b/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java index e40154f..25de22e 100644 --- a/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java +++ b/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java @@ -328,6 +328,13 @@ public class SimpleOperatorTest extends LanguageTestSupport { assertPredicate("${in.header.foo} not contains 'abc'", false); assertPredicate("${in.header.foo} not contains 'def'", true); } + + public void testContainsIgnoreCase() throws Exception { + assertPredicate("${in.header.foo} ~~ 'A'", true); + assertPredicate("${in.header.foo} ~~ 'Ab'", true); + assertPredicate("${in.header.foo} ~~ 'Abc'", true); + assertPredicate("${in.header.foo} ~~ 'defG'", false); + } public void testRegex() throws Exception { assertPredicate("${in.header.foo} regex '^a..$'", true); http://git-wip-us.apache.org/repos/asf/camel/blob/d97c9625/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/ContainsIgnoreCaseTest.java ---------------------------------------------------------------------- diff --git a/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/ContainsIgnoreCaseTest.java b/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/ContainsIgnoreCaseTest.java new file mode 100644 index 0000000..1a29133 --- /dev/null +++ b/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/ContainsIgnoreCaseTest.java @@ -0,0 +1,85 @@ +/** + * 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.StringHelper; +import org.junit.Test; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +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 StringHelper}. + * <p/> + * Thanks to this SO answer: https://stackoverflow.com/questions/30485856/how-to-run-jmh-from-inside-junit-tests + */ +public class ContainsIgnoreCaseTest { + + @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(true) + .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 + @Measurement(batchSize = 1000000) + public void benchmark(BenchmarkState state, Blackhole bh) { + bh.consume(StringHelper.containsIgnoreCase("abc", "A")); + bh.consume(StringHelper.containsIgnoreCase("abc", "aB")); + bh.consume(StringHelper.containsIgnoreCase("abc", "aBc")); + bh.consume(StringHelper.containsIgnoreCase("abc", "ad")); + bh.consume(StringHelper.containsIgnoreCase("abc", "abD")); + bh.consume(StringHelper.containsIgnoreCase("abc", "ABD")); + } + +}
