All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.support.ObjectHelper Maven / Gradle / Ivy

There is a newer version: 4.8.0
Show newest version
/*
 * 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.support;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.Ordered;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.TypeConverter;
import org.apache.camel.util.Scanner;
import org.apache.camel.util.StringHelper;

/**
 * A number of useful helper methods for working with Objects
 */
public final class ObjectHelper {

    static {
        PARENTHESIS_PATTERN = Pattern.compile(",(?!(?:[^\\(,]|[^\\)],[^\\)])+\\))");
    }

    private static final Pattern PARENTHESIS_PATTERN;
    private static final String DEFAULT_DELIMITER = ",";
    private static final char DEFAULT_DELIMITER_CHAR = ',';

    /**
     * Utility classes should not have a public constructor.
     */
    private ObjectHelper() {
    }

    /**
     * A helper method for comparing objects for equality in which it uses type coercion to coerce types between the
     * left and right values. This allows you test for equality for example with a String and Integer type as Camel will
     * be able to coerce the types.
     */
    public static boolean typeCoerceEquals(TypeConverter converter, Object leftValue, Object rightValue) {
        return typeCoerceEquals(converter, leftValue, rightValue, false);
    }

    /**
     * A helper method for comparing objects for equality in which it uses type coercion to coerce types between the
     * left and right values. This allows you test for equality for example with a String and Integer type as Camel will
     * be able to coerce the types.
     */
    public static boolean typeCoerceEquals(TypeConverter converter, Object leftValue, Object rightValue, boolean ignoreCase) {
        // sanity check
        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;
        }

        // optimize for common combinations of comparing numbers
        if (leftValue instanceof String && rightValue instanceof String) {
            String leftNum = (String) leftValue;
            String rightNum = (String) rightValue;
            if (isNumber(leftNum) && isNumber(rightNum)) {
                // favour to use numeric comparison
                Long num1 = Long.parseLong(leftNum);
                Long num2 = Long.parseLong(rightNum);
                return num1.compareTo(num2) == 0;
            }
            if (ignoreCase) {
                return leftNum.compareToIgnoreCase(rightNum) == 0;
            } else {
                return leftNum.compareTo(rightNum) == 0;
            }
        } else if (leftValue instanceof Integer && rightValue instanceof Integer) {
            Integer leftNum = (Integer) leftValue;
            Integer rightNum = (Integer) rightValue;
            return leftNum.compareTo(rightNum) == 0;
        } else if (leftValue instanceof Long && rightValue instanceof Long) {
            Long leftNum = (Long) leftValue;
            Long rightNum = (Long) rightValue;
            return leftNum.compareTo(rightNum) == 0;
        } else if (leftValue instanceof Double && rightValue instanceof Double) {
            Double leftNum = (Double) leftValue;
            Double rightNum = (Double) rightValue;
            return leftNum.compareTo(rightNum) == 0;
        } else if ((rightValue instanceof Integer || rightValue instanceof Long) &&
                leftValue instanceof String && isNumber((String) leftValue)) {
            if (rightValue instanceof Integer) {
                Integer leftNum = Integer.valueOf((String) leftValue);
                Integer rightNum = (Integer) rightValue;
                return leftNum.compareTo(rightNum) == 0;
            } else {
                Long leftNum = Long.valueOf((String) leftValue);
                Long rightNum = (Long) rightValue;
                return leftNum.compareTo(rightNum) == 0;
            }
        } else if (rightValue instanceof String &&
                (leftValue instanceof Integer || leftValue instanceof Long) && isNumber((String) rightValue)) {
            if (leftValue instanceof Integer) {
                Integer leftNum = (Integer) leftValue;
                Integer rightNum = Integer.valueOf((String) rightValue);
                return leftNum.compareTo(rightNum) == 0;
            } else {
                Long leftNum = (Long) leftValue;
                Long rightNum = Long.valueOf((String) rightValue);
                return leftNum.compareTo(rightNum) == 0;
            }
        } else if (rightValue instanceof Double && leftValue instanceof String && isFloatingNumber((String) leftValue)) {
            Double leftNum = Double.valueOf((String) leftValue);
            Double rightNum = (Double) rightValue;
            return leftNum.compareTo(rightNum) == 0;
        } else if (rightValue instanceof Boolean && leftValue instanceof String) {
            Boolean leftBool = Boolean.valueOf((String) leftValue);
            Boolean rightBool = (Boolean) rightValue;
            return leftBool.compareTo(rightBool) == 0;
        } else if (rightValue instanceof String && leftValue instanceof Boolean) {
            Boolean leftBool = (Boolean) leftValue;
            Boolean rightBool = Boolean.valueOf((String) rightValue);
            return leftBool.compareTo(rightBool) == 0;
        }

        // try without type coerce
        boolean answer = org.apache.camel.util.ObjectHelper.equal(leftValue, rightValue, ignoreCase);
        if (answer) {
            return true;
        }

        // are they same type, if so return false as the equals returned false
        if (leftValue.getClass().isInstance(rightValue)) {
            return false;
        }

        // convert left to right
        Object value = converter.tryConvertTo(rightValue.getClass(), leftValue);
        answer = org.apache.camel.util.ObjectHelper.equal(value, rightValue, ignoreCase);
        if (answer) {
            return true;
        }

        // convert right to left
        value = converter.tryConvertTo(leftValue.getClass(), rightValue);
        answer = org.apache.camel.util.ObjectHelper.equal(leftValue, value, ignoreCase);
        return answer;
    }

    /**
     * A helper method for comparing objects for inequality in which it uses type coercion to coerce types between the
     * left and right values. This allows you test for inequality for example with a String and Integer type as Camel
     * will be able to coerce the types.
     */
    public static boolean typeCoerceNotEquals(TypeConverter converter, Object leftValue, Object rightValue) {
        return !typeCoerceEquals(converter, leftValue, rightValue);
    }

    /**
     * A helper method for comparing objects ordering in which it uses type coercion to coerce types between the left
     * and right values. This allows you test for ordering for example with a String and Integer type as Camel will be
     * able to coerce the types.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static int typeCoerceCompare(TypeConverter converter, Object leftValue, Object rightValue) {

        // optimize for common combinations of comparing numbers
        if (leftValue instanceof String && rightValue instanceof String) {
            String leftNum = (String) leftValue;
            String rightNum = (String) rightValue;
            if (isNumber(leftNum) && isNumber(rightNum)) {
                // favour to use numeric comparison
                Long num1 = Long.parseLong(leftNum);
                Long num2 = Long.parseLong(rightNum);
                return num1.compareTo(num2);
            }
            return leftNum.compareTo(rightNum);
        } else if (leftValue instanceof Integer && rightValue instanceof Integer) {
            Integer leftNum = (Integer) leftValue;
            Integer rightNum = (Integer) rightValue;
            return leftNum.compareTo(rightNum);
        } else if (leftValue instanceof Long && rightValue instanceof Long) {
            Long leftNum = (Long) leftValue;
            Long rightNum = (Long) rightValue;
            return leftNum.compareTo(rightNum);
        } else if (leftValue instanceof Double && rightValue instanceof Double) {
            Double leftNum = (Double) leftValue;
            Double rightNum = (Double) rightValue;
            return leftNum.compareTo(rightNum);
        } else if ((rightValue instanceof Integer || rightValue instanceof Long) &&
                leftValue instanceof String && isNumber((String) leftValue)) {
            if (rightValue instanceof Integer) {
                Integer leftNum = Integer.valueOf((String) leftValue);
                Integer rightNum = (Integer) rightValue;
                return leftNum.compareTo(rightNum);
            } else {
                Long leftNum = Long.valueOf((String) leftValue);
                Long rightNum = (Long) rightValue;
                return leftNum.compareTo(rightNum);
            }
        } else if (rightValue instanceof String &&
                (leftValue instanceof Integer || leftValue instanceof Long) && isNumber((String) rightValue)) {
            if (leftValue instanceof Integer) {
                Integer leftNum = (Integer) leftValue;
                Integer rightNum = Integer.valueOf((String) rightValue);
                return leftNum.compareTo(rightNum);
            } else {
                Long leftNum = (Long) leftValue;
                Long rightNum = Long.valueOf((String) rightValue);
                return leftNum.compareTo(rightNum);
            }
        } else if (rightValue instanceof Double && leftValue instanceof String && isFloatingNumber((String) leftValue)) {
            Double leftNum = Double.valueOf((String) leftValue);
            Double rightNum = (Double) rightValue;
            return leftNum.compareTo(rightNum);
        } else if (rightValue instanceof Boolean && leftValue instanceof String) {
            Boolean leftBool = Boolean.valueOf((String) leftValue);
            Boolean rightBool = (Boolean) rightValue;
            return leftBool.compareTo(rightBool);
        } else if (rightValue instanceof String && leftValue instanceof Boolean) {
            Boolean leftBool = (Boolean) leftValue;
            Boolean rightBool = Boolean.valueOf((String) rightValue);
            return leftBool.compareTo(rightBool);
        }

        // if both values is numeric then compare using numeric
        Long leftNum = converter.tryConvertTo(Long.class, leftValue);
        Long rightNum = converter.tryConvertTo(Long.class, rightValue);
        if (leftNum != null && rightNum != null) {
            return leftNum.compareTo(rightNum);
        }

        // also try with floating point numbers
        Double leftDouble = converter.tryConvertTo(Double.class, leftValue);
        Double rightDouble = converter.tryConvertTo(Double.class, rightValue);
        if (leftDouble != null && rightDouble != null) {
            return leftDouble.compareTo(rightDouble);
        }

        // prefer to NOT coerce to String so use the type which is not String
        // for example if we are comparing String vs Integer then prefer to coerce to Integer
        // as all types can be converted to String which does not work well for comparison
        // as eg "10" < 6 would return true, where as 10 < 6 will return false.
        // if they are both String then it doesn't matter
        if (rightValue instanceof String && !(leftValue instanceof String)) {
            // if right is String and left is not then flip order (remember to * -1 the result then)
            return typeCoerceCompare(converter, rightValue, leftValue) * -1;
        }

        // prefer to coerce to the right hand side at first
        if (rightValue instanceof Comparable) {
            Object value = converter.tryConvertTo(rightValue.getClass(), leftValue);
            if (value != null) {
                return ((Comparable) rightValue).compareTo(value) * -1;
            }
        }

        // then fallback to the left hand side
        if (leftValue instanceof Comparable) {
            Object value = converter.tryConvertTo(leftValue.getClass(), rightValue);
            if (value != null) {
                return ((Comparable) leftValue).compareTo(value);
            }
        }

        // use regular compare
        return compare(leftValue, rightValue);
    }

    /**
     * Checks whether the text is an integer number
     */
    public static boolean isNumber(String text) {
        if (text == null || text.isEmpty()) {
            return false;
        }
        if (text.equals("-")) {
            return false;
        }
        for (int i = 0; i < text.length(); i++) {
            char ch = text.charAt(i);
            if (i == 0 && ch == '-') {
                // skip leading negative
            } else if (!Character.isDigit(ch)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Checks whether the text is a float point number
     */
    public static boolean isFloatingNumber(String text) {
        if (text == null || text.isEmpty()) {
            return false;
        }
        if (text.equals("-")) {
            return false;
        }
        boolean dots = false;
        for (int i = 0; i < text.length(); i++) {
            char ch = text.charAt(i);
            if (i == 0 && ch == '-') {
                // skip leading negative
            } else if (ch == '.') {
                if (dots) {
                    return false;
                }
                dots = true;
            } else if (!Character.isDigit(ch)) {
                return false;
            }
        }
        return true;
    }

    /**
     * A helper method to invoke a method via reflection and wrap any exceptions as {@link RuntimeCamelException}
     * instances
     *
     * @param  method     the method to invoke
     * @param  instance   the object instance (or null for static methods)
     * @param  parameters the parameters to the method
     * @return            the result of the method invocation
     */
    public static Object invokeMethod(Method method, Object instance, Object... parameters) {
        try {
            if (parameters != null) {
                return method.invoke(instance, parameters);
            } else {
                return method.invoke(instance);
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeCamelException(e);
        } catch (InvocationTargetException e) {
            throw RuntimeCamelException.wrapRuntimeCamelException(e.getCause());
        }
    }

    /**
     * A helper method to invoke a method via reflection in a safe way by allowing to invoke methods that are not
     * accessible by default and wrap any exceptions as {@link RuntimeCamelException} instances
     *
     * @param  method     the method to invoke
     * @param  instance   the object instance (or null for static methods)
     * @param  parameters the parameters to the method
     * @return            the result of the method invocation
     */
    public static Object invokeMethodSafe(Method method, Object instance, Object... parameters)
            throws InvocationTargetException, IllegalAccessException {
        Object answer;
        if (!method.isAccessible()) {
            method.setAccessible(true);
        }
        if (parameters != null) {
            answer = method.invoke(instance, parameters);
        } else {
            answer = method.invoke(instance);
        }
        return answer;
    }

    /**
     * A helper method to create a new instance of a type using the default constructor arguments.
     */
    public static  T newInstance(Class type) {
        try {
            return type.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeCamelException(e);
        }
    }

    /**
     * A helper method to create a new instance of a type using the default constructor arguments.
     */
    public static  T newInstance(Class actualType, Class expectedType) {
        try {
            Object value = actualType.getDeclaredConstructor().newInstance();
            return org.apache.camel.util.ObjectHelper.cast(expectedType, value);
        } catch (Exception e) {
            throw new RuntimeCamelException(e);
        }
    }

    /**
     * A helper method for performing an ordered comparison on the objects handling nulls and objects which do not
     * handle sorting gracefully
     */
    public static int compare(Object a, Object b) {
        return compare(a, b, false);
    }

    /**
     * A helper method for performing an ordered comparison on the objects handling nulls and objects which do not
     * handle sorting gracefully
     *
     * @param a          the first object
     * @param b          the second object
     * @param ignoreCase ignore case for string comparison
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static int compare(Object a, Object b, boolean ignoreCase) {
        if (a == b) {
            return 0;
        }
        if (a == null) {
            return -1;
        }
        if (b == null) {
            return 1;
        }
        if (a instanceof Ordered && b instanceof Ordered) {
            return ((Ordered) a).getOrder() - ((Ordered) b).getOrder();
        }
        if (ignoreCase && a instanceof String && b instanceof String) {
            return ((String) a).compareToIgnoreCase((String) b);
        }
        if (a instanceof Comparable) {
            Comparable comparable = (Comparable) a;
            return comparable.compareTo(b);
        }
        int answer = a.getClass().getName().compareTo(b.getClass().getName());
        if (answer == 0) {
            answer = a.hashCode() - b.hashCode();
        }
        return answer;
    }

    /**
     * Calling the Callable with the setting of TCCL with the camel context application classloader.
     *
     * @param  call     the Callable instance
     * @param  exchange the exchange
     * @return          the result of Callable return
     */
    public static Object callWithTCCL(Callable call, Exchange exchange) throws Exception {
        ClassLoader apcl = null;
        if (exchange != null && exchange.getContext() != null) {
            apcl = exchange.getContext().getApplicationContextClassLoader();
        }
        return callWithTCCL(call, apcl);
    }

    /**
     * Calling the Callable with the setting of TCCL with a given classloader.
     *
     * @param  call        the Callable instance
     * @param  classloader the class loader
     * @return             the result of Callable return
     */
    public static Object callWithTCCL(Callable call, ClassLoader classloader) throws Exception {
        final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
        try {
            if (classloader != null) {
                Thread.currentThread().setContextClassLoader(classloader);
            }
            return call.call();
        } finally {
            Thread.currentThread().setContextClassLoader(tccl);
        }
    }

    /**
     * Creates an iterable over the value if the value is a collection, an Object[], a String with values separated by
     * comma, or a primitive type array; otherwise to simplify the caller's code, we just create a singleton collection
     * iterator over a single value
     * 

* Will default use comma for String separating String values. This method does not allow empty values * * @param value the value * @return the iterable */ public static Iterable createIterable(Object value) { return createIterable(value, DEFAULT_DELIMITER); } /** * Creates an iterable over the value if the value is a collection, an Object[], a String with values separated by * the given delimiter, or a primitive type array; otherwise to simplify the caller's code, we just create a * singleton collection iterator over a single value *

* This method does not allow empty values * * @param value the value * @param delimiter delimiter for separating String values * @return the iterable */ public static Iterable createIterable(Object value, String delimiter) { return createIterable(value, delimiter, false); } public static Iterable createIterable(String value) { return createIterable(value, DEFAULT_DELIMITER); } public static Iterable createIterable(String value, String delimiter) { return createIterable(value, delimiter, false); } public static Iterable createIterable(String value, String delimiter, boolean allowEmptyValues) { return createIterable(value, delimiter, allowEmptyValues, false); } public static Iterable createIterable(String value, String delimiter, boolean allowEmptyValues, boolean pattern) { if (value == null) { return Collections.emptyList(); } else if (delimiter != null && (pattern || value.contains(delimiter))) { // if its the default delimiter and the value has parenthesis if (DEFAULT_DELIMITER.equals(delimiter)) { if (value.indexOf('(') != -1 && value.indexOf(')') != -1) { // we use the default delimiter which is a comma, then cater for bean expressions with OGNL // which may have balanced parentheses pairs as well. // if the value contains parentheses we need to balance those, to avoid iterating // in the middle of parentheses pair, so use this regular expression (a bit hard to read) // the regexp will split by comma, but honor parentheses pair that may include commas // as well, eg if value = "bean=foo?method=killer(a,b),bean=bar?method=great(a,b)" // then the regexp will split that into two: // -> bean=foo?method=killer(a,b) // -> bean=bar?method=great(a,b) // http://stackoverflow.com/questions/1516090/splitting-a-title-into-separate-parts return () -> new Scanner(value, PARENTHESIS_PATTERN); } else { // optimized split string on default delimiter int count = StringHelper.countChar(value, DEFAULT_DELIMITER_CHAR) + 1; return () -> StringHelper.splitOnCharacterAsIterator(value, DEFAULT_DELIMITER_CHAR, count); } } else if (pattern) { return () -> new StringIteratorForPattern(value, delimiter); } return () -> new StringIterator(value, delimiter); } else if (allowEmptyValues || org.apache.camel.util.ObjectHelper.isNotEmpty(value)) { return Collections.singletonList(value); } else { return Collections.emptyList(); } } /** * Creates an iterator over the value if the value is a {@link Stream}, collection, an Object[], a String with * values separated by comma, or a primitive type array; otherwise to simplify the caller's code, we just create a * singleton collection iterator over a single value *

* Will default use comma for String separating String values. This method does not allow empty values * * @param value the value * @return the iterator */ public static Iterator createIterator(Object value) { return createIterator(value, DEFAULT_DELIMITER); } /** * Creates an iterator over the value if the value is a {@link Stream}, collection, an Object[], a String with * values separated by the given delimiter, or a primitive type array; otherwise to simplify the caller's code, we * just create a singleton collection iterator over a single value *

* This method does not allow empty values * * @param value the value * @param delimiter delimiter for separating String values * @return the iterator */ public static Iterator createIterator(Object value, String delimiter) { return createIterator(value, delimiter, false); } /** * Creates an iterator over the value if the value is a {@link Stream}, collection, an Object[], a String with * values separated by the given delimiter, or a primitive type array; otherwise to simplify the caller's code, we * just create a singleton collection iterator over a single value * *

* In case of primitive type arrays the returned {@code Iterator} iterates over the corresponding Java primitive * wrapper objects of the given elements inside the {@code value} array. That's we get an autoboxing of the * primitive types here for free as it's also the case in Java language itself. * * @param value the value * @param delimiter delimiter for separating String values * @param allowEmptyValues whether to allow empty values * @return the iterator */ public static Iterator createIterator(Object value, String delimiter, boolean allowEmptyValues) { if (value instanceof Stream) { return ((Stream) value).iterator(); } return createIterable(value, delimiter, allowEmptyValues, false).iterator(); } /** * Creates an iterator over the value if the value is a {@link Stream}, collection, an Object[], a String with * values separated by the given delimiter, or a primitive type array; otherwise to simplify the caller's code, we * just create a singleton collection iterator over a single value * *

* In case of primitive type arrays the returned {@code Iterator} iterates over the corresponding Java primitive * wrapper objects of the given elements inside the {@code value} array. That's we get an autoboxing of the * primitive types here for free as it's also the case in Java language itself. * * @param value the value * @param delimiter delimiter for separating String values * @param allowEmptyValues whether to allow empty values * @param pattern whether the delimiter is a pattern * @return the iterator */ public static Iterator createIterator( Object value, String delimiter, boolean allowEmptyValues, boolean pattern) { if (value instanceof Stream) { return ((Stream) value).iterator(); } return createIterable(value, delimiter, allowEmptyValues, pattern).iterator(); } /** * Creates an iterable over the value if the value is a collection, an Object[], a String with values separated by * the given delimiter, or a primitive type array; otherwise to simplify the caller's code, we just create a * singleton collection iterator over a single value * *

* In case of primitive type arrays the returned {@code Iterable} iterates over the corresponding Java primitive * wrapper objects of the given elements inside the {@code value} array. That's we get an autoboxing of the * primitive types here for free as it's also the case in Java language itself. * * @param value the value * @param delimiter delimiter for separating String values * @param allowEmptyValues whether to allow empty values * @return the iterable * @see Iterable */ public static Iterable createIterable( Object value, String delimiter, final boolean allowEmptyValues) { return createIterable(value, delimiter, allowEmptyValues, false); } /** * Creates an iterable over the value if the value is a collection, an Object[], a String with values separated by * the given delimiter, or a primitive type array; otherwise to simplify the caller's code, we just create a * singleton collection iterator over a single value *

* In case of primitive type arrays the returned {@code Iterable} iterates over the corresponding Java primitive * wrapper objects of the given elements inside the {@code value} array. That's we get an autoboxing of the * primitive types here for free as it's also the case in Java language itself. * * @param value the value * @param delimiter delimiter for separating String values * @param allowEmptyValues whether to allow empty values * @param pattern whether the delimiter is a pattern * @return the iterable * @see Iterable */ @SuppressWarnings("unchecked") public static Iterable createIterable( Object value, String delimiter, final boolean allowEmptyValues, final boolean pattern) { // if its a message than we want to iterate its body if (value instanceof Message) { value = ((Message) value).getBody(); } if (value == null) { return Collections.emptyList(); } else if (value instanceof Iterator) { final Iterator iterator = (Iterator) value; return (Iterable) () -> iterator; } else if (value instanceof Iterable) { return (Iterable) value; } else if (value instanceof Map) { Map map = (Map) value; return map.entrySet(); } else if (value.getClass().isArray()) { if (org.apache.camel.util.ObjectHelper.isPrimitiveArrayType(value.getClass())) { final Object array = value; return (Iterable) () -> new Iterator() { private int idx; public boolean hasNext() { return idx < Array.getLength(array); } public Object next() { if (!hasNext()) { throw new NoSuchElementException( "no more element available for '" + array + "' at the index " + idx); } return Array.get(array, idx++); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } else { return Arrays.asList((Object[]) value); } } else if (value instanceof NodeList) { // lets iterate through DOM results after performing XPaths final NodeList nodeList = (NodeList) value; return (Iterable) () -> new Iterator() { private int idx; public boolean hasNext() { return idx < nodeList.getLength(); } public Node next() { if (!hasNext()) { throw new NoSuchElementException( "no more element available for '" + nodeList + "' at the index " + idx); } return nodeList.item(idx++); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } else if (value instanceof String) { final String s = (String) value; // this code is optimized to only use a Scanner if needed, eg there is a delimiter if (delimiter != null && (pattern || s.contains(delimiter))) { // if its the default delimiter and the value has parenthesis if (DEFAULT_DELIMITER.equals(delimiter)) { if (s.indexOf('(') != -1 && s.indexOf(')') != -1) { // we use the default delimiter which is a comma, then cater for bean expressions with OGNL // which may have balanced parentheses pairs as well. // if the value contains parentheses we need to balance those, to avoid iterating // in the middle of parentheses pair, so use this regular expression (a bit hard to read) // the regexp will split by comma, but honor parentheses pair that may include commas // as well, eg if value = "bean=foo?method=killer(a,b),bean=bar?method=great(a,b)" // then the regexp will split that into two: // -> bean=foo?method=killer(a,b) // -> bean=bar?method=great(a,b) // http://stackoverflow.com/questions/1516090/splitting-a-title-into-separate-parts return (Iterable) () -> new Scanner(s, PARENTHESIS_PATTERN); } else { // optimized split string on default delimiter int count = StringHelper.countChar(s, DEFAULT_DELIMITER_CHAR) + 1; return (Iterable) () -> StringHelper.splitOnCharacterAsIterator(s, DEFAULT_DELIMITER_CHAR, count); } } else if (pattern) { return (Iterable) () -> new StringIteratorForPattern(s, delimiter); } return (Iterable) () -> new StringIterator(s, delimiter); } else { return (Iterable) () -> { // use a plain iterator that returns the value as is as there are only a single value return new Iterator() { private int idx; public boolean hasNext() { return idx == 0 && (allowEmptyValues || org.apache.camel.util.ObjectHelper.isNotEmpty(s)); } public Object next() { if (!hasNext()) { throw new NoSuchElementException( "no more element available for '" + s + "' at the index " + idx); } idx++; return s; } @Override public void remove() { throw new UnsupportedOperationException(); } }; }; } } else { return Collections.singletonList(value); } } /** * Returns true if the collection contains the specified value * * @deprecated use {@link #typeCoerceContains(TypeConverter, Object, Object, boolean)} */ @Deprecated public static boolean contains(Object collectionOrArray, Object value) { // favor String types if (collectionOrArray instanceof StringBuffer || collectionOrArray instanceof StringBuilder) { collectionOrArray = collectionOrArray.toString(); } if (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 str.contains(subStr); } else { Iterator iter = createIterator(collectionOrArray); while (iter.hasNext()) { if (org.apache.camel.util.ObjectHelper.equal(value, iter.next())) { return true; } } } return false; } /** * Returns true if the collection contains the specified value */ public static boolean typeCoerceContains( TypeConverter typeConverter, Object collectionOrArray, Object value, boolean ignoreCase) { // favor String types if (collectionOrArray instanceof StringBuffer || collectionOrArray instanceof StringBuilder) { collectionOrArray = collectionOrArray.toString(); } if (value instanceof StringBuffer || value instanceof StringBuilder) { value = value.toString(); } if (collectionOrArray instanceof Collection) { Collection collection = (Collection) collectionOrArray; if (ignoreCase) { String lower = value.toString().toLowerCase(Locale.ENGLISH); return collection.stream().anyMatch(c -> c.toString().toLowerCase(Locale.ENGLISH).contains(lower)); } else { return collection.contains(value); } } else if (collectionOrArray instanceof String) { String str = (String) collectionOrArray; String subStr; if (value instanceof String) { subStr = (String) value; } else { subStr = typeConverter.tryConvertTo(String.class, value); } if (subStr != null) { if (ignoreCase) { String lower = subStr.toLowerCase(Locale.ENGLISH); return str.toLowerCase(Locale.ENGLISH).contains(lower); } else { return str.contains(subStr); } } } Iterator iter = createIterator(collectionOrArray); while (iter.hasNext()) { if (typeCoerceEquals(typeConverter, value, iter.next(), ignoreCase)) { return true; } } return false; } /** * An {@link Iterator} to split an input {@code String} content according to a specific {@code String} literal as * separator. */ private static class StringIterator implements Iterator { /** * Flag indicating that the indexes have already been computed. */ private boolean computed; /** * The current {@code from} index. */ private int from; /** * The current {@code to} index. */ private int to; /** * The content to split. */ private final String content; /** * The separator to use when splitting the content. */ private final String separator; /** * The length of the separator. */ private final int separatorLength; /** * The length of the part of the content to split. */ private final int contentLength; /** * Construct a {@code StringIterator} with the specified content and separator. * * @param content the content to split. * @param separator the separator to use when splitting the content. */ StringIterator(String content, String separator) { this.content = content; this.separator = separator; this.separatorLength = separator.length(); boolean skipStart = content.startsWith(separator); boolean skipEnd = content.endsWith(separator); if (skipStart && skipEnd) { this.from = separatorLength; this.contentLength = content.length() - separatorLength; } else if (skipStart) { this.from = separatorLength; this.contentLength = content.length(); } else if (skipEnd) { this.contentLength = content.length() - separatorLength; } else { this.contentLength = content.length(); } } @Override public boolean hasNext() { if (computed) { return to != -1; } else if (to == -1) { return false; } int index = content.indexOf(separator, from); if (index == -1 || index == contentLength) { to = contentLength; } else { to = index; } computed = true; return true; } @Override public String next() { if (!hasNext()) { throw new NoSuchElementException(); } String answer; if (to == contentLength) { answer = content.substring(from, contentLength); to = -1; } else { answer = content.substring(from, to); from = to + separatorLength; } computed = false; return answer; } } /** * An {@link Iterator} to split an input {@code String} content according to a specific pattern as separator. */ private static class StringIteratorForPattern implements Iterator { /** * Flag indicating that the indexes have already been computed. */ private boolean computed; /** * The current {@code from} index. */ private int from; /** * The current {@code to} index. */ private int to; /** * The content to split. */ private final String content; /** * The matcher that will match the content to split against the pattern used as separator. */ private final Matcher matcher; /** * The length of the part of the content to split. */ private int contentLength; /** * Construct a {@code StringIterator} with the specified content and separator. * * @param content the content to split. * @param pattern the pattern to use when splitting the content. */ StringIteratorForPattern(String content, String pattern) { this.content = content; this.matcher = Pattern.compile(pattern).matcher(content); matcher.useTransparentBounds(true); matcher.useAnchoringBounds(false); this.contentLength = content.length(); } @Override public boolean hasNext() { for (;;) { if (computed) { return to != -1; } else if (to == -1) { return false; } if (matcher.find(from)) { to = matcher.start(); if (from == to) { from = matcher.end(); continue; } else if (matcher.end() == contentLength) { contentLength = to; } } else { to = contentLength; } computed = true; return true; } } @Override public String next() { if (!hasNext()) { throw new NoSuchElementException(); } String answer; if (to == contentLength) { answer = content.substring(from, contentLength); to = -1; } else { answer = content.substring(from, to); from = matcher.end(); } computed = false; return answer; } } }