Skip to content

Commit

Permalink
CAMEL-11420-Add contains ignore case operator to simple language
Browse files Browse the repository at this point in the history
CAMEL-11420-add jmh test

CAMEL-11420 - null check added for containsIgnoreCase in StringHelper

CAMEL-11420 - CR fixes
  • Loading branch information
onderson committed Jun 21, 2017
1 parent ea30e54 commit 9c0fd01
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 2 deletions.
2 changes: 1 addition & 1 deletion buildingtools/src/main/resources/camel-checkstyle.xml
Expand Up @@ -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>

Expand Down
Expand Up @@ -330,6 +330,27 @@ protected String getOperationText() {
}
};
}

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)) {
Expand Down
Expand Up @@ -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"));
Expand Down
Expand Up @@ -96,6 +96,8 @@ public Expression createExpression(String expression) {
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) {
Expand Down
Expand Up @@ -21,7 +21,8 @@
*/
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) {
Expand All @@ -43,6 +44,8 @@ public static BinaryOperatorType asOperator(String text) {
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)) {
Expand Down Expand Up @@ -86,6 +89,8 @@ public static String getOperatorText(BinaryOperatorType operator) {
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) {
Expand Down Expand Up @@ -174,6 +179,8 @@ public static ParameterType[] supportedParameterTypes(BinaryOperatorType operato
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) {
Expand Down
37 changes: 37 additions & 0 deletions camel-core/src/main/java/org/apache/camel/util/ObjectHelper.java
Expand Up @@ -195,6 +195,13 @@ public static int typeCoerceCompare(TypeConverter converter, Object leftValue, O
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
Expand Down Expand Up @@ -647,6 +654,36 @@ public static boolean contains(Object collectionOrArray, Object value) {
}
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
Expand Down
35 changes: 35 additions & 0 deletions camel-core/src/main/java/org/apache/camel/util/StringHelper.java
Expand Up @@ -672,5 +672,40 @@ public static String trimToNull(final String given) {

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;
}

}
Expand Up @@ -328,6 +328,13 @@ public void testNotContains() throws Exception {
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);
Expand Down
@@ -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"));
}

}

0 comments on commit 9c0fd01

Please sign in to comment.