com.jkoolcloud.tnt4j.streams.utils.Utils.java Source code

Java tutorial

Introduction

Here is the source code for com.jkoolcloud.tnt4j.streams.utils.Utils.java

Source

/*
 * Copyright 2014-2017 JKOOL, LLC.
 *
 * Licensed 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 com.jkoolcloud.tnt4j.streams.utils;

import java.io.*;
import java.lang.reflect.Field;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.HexDump;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Node;

import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.internal.LinkedTreeMap;
import com.jkoolcloud.tnt4j.core.OpType;
import com.jkoolcloud.tnt4j.streams.parsers.MessageType;

/**
 * General utility methods used by TNT4J-Streams.
 *
 * @version $Revision: 1 $
 */
public final class Utils extends com.jkoolcloud.tnt4j.utils.Utils {

    private static final String TAG_DELIM = ","; // NON-NLS
    private static final String VALUE_DELIM = "\\|"; // NON-NLS
    private static final String HEX_PREFIX = "0x"; // NON-NLS
    private static final Pattern LINE_ENDINGS_PATTERN = Pattern.compile("(\\r\\n|\\r|\\n)"); // NON-NLS
    private static final Pattern UUID_PATTERN = Pattern
            .compile("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"); // NON-NLS
    private static final Pattern CFG_VAR_PATTERN = Pattern.compile("\\$\\{(\\w+)\\}"); // NON-NLS
    private static final Pattern EXPR_VAR_PATTERN = CFG_VAR_PATTERN;// Pattern.compile("\\$(\\w+)"); // NON-NLS

    /**
     * Default floating point numbers equality comparison difference tolerance {@value}.
     */
    public static final double DEFAULT_EPSILON = 0.000001;

    /**
     * Constant for system dependent line separator symbol.
     */
    public static final String NEW_LINE = System.getProperty("line.separator");

    private Utils() {
    }

    /**
     * Base64 encodes the specified sequence of bytes.
     *
     * @param src
     *            byte sequence to encode
     * @return encoded byte sequence
     */
    public static byte[] base64Encode(byte[] src) {
        return Base64.encodeBase64(src);
    }

    /**
     * Base64 encodes the specified sequence of bytes as string.
     *
     * @param src
     *            byte sequence to encode
     * @return encoded string
     */
    public static String base64EncodeStr(byte[] src) {
        return Base64.encodeBase64String(src);
    }

    /**
     * Base64 decodes the specified sequence of bytes.
     *
     * @param src
     *            byte sequence to decode
     * @return decoded byte sequence
     */
    public static byte[] base64Decode(byte[] src) {
        return Base64.decodeBase64(src);
    }

    /**
     * Base64 decodes the specified encoded data string.
     *
     * @param src
     *            base64 encoded string to decode
     * @return decoded byte sequence
     */
    public static byte[] base64Decode(String src) {
        return Base64.decodeBase64(src);
    }

    /**
     * Converts an array of bytes into a string representing the hexadecimal bytes values.
     *
     * @param src
     *            byte sequence to encode
     * @return encoded bytes hex string
     */
    public static String encodeHex(byte[] src) {
        return Hex.encodeHexString(src);
    }

    /**
     * Converts a string representing hexadecimal values into an array of bytes.
     *
     * @param str
     *            String to convert
     * @return decoded byte sequence
     */
    public static byte[] decodeHex(String str) {
        byte[] ba = null;
        try {
            if (str != null && str.startsWith(HEX_PREFIX)) {
                str = str.substring(HEX_PREFIX.length());
            }
            ba = Hex.decodeHex((str == null ? "" : str).toCharArray());
        } catch (DecoderException e) {
        }
        return ba;
    }

    private static final MessageDigest MSG_DIGEST = getMD5Digester();

    /**
     * Generates a new unique message signature. This signature is expected to be used for creating a new message
     * instance, and is intended to uniquely identify the message regardless of which application is processing it.
     * <p>
     * It is up to the individual stream to determine which of these attributes is available/required to uniquely
     * identify a message. In order to identify a message within two different transports, the streams for each
     * transport must provide the same values.
     *
     * @param msgType
     *            message type
     * @param msgFormat
     *            message format
     * @param msgId
     *            message identifier
     * @param userId
     *            user that originated the message
     * @param putApplType
     *            type of application that originated the message
     * @param putApplName
     *            name of application that originated the message
     * @param putDate
     *            date (GMT) the message was originated
     * @param putTime
     *            time (GMT) the message was originated
     * @return unique message signature
     */
    public static String computeSignature(MessageType msgType, String msgFormat, byte[] msgId, String userId,
            String putApplType, String putApplName, String putDate, String putTime) {
        synchronized (MSG_DIGEST) {
            return computeSignature(MSG_DIGEST, msgType, msgFormat, msgId, userId, putApplType, putApplName,
                    putDate, putTime);
        }
    }

    /**
     * Generates a new unique message signature. This signature is expected to be used for creating a new message
     * instance, and is intended to uniquely identify the message regardless of which application is processing it.
     * <p>
     * It is up to the individual stream to determine which of these attributes is available/required to uniquely
     * identify a message. In order to identify a message within two different transports, the streams for each
     * transport must provide the same values.
     *
     * @param _msgDigest
     *            message type
     * @param msgType
     *            message type
     * @param msgFormat
     *            message format
     * @param msgId
     *            message identifier
     * @param userId
     *            user that originated the message
     * @param putApplType
     *            type of application that originated the message
     * @param putApplName
     *            name of application that originated the message
     * @param putDate
     *            date (GMT) the message was originated
     * @param putTime
     *            time (GMT) the message was originated
     * @return unique message signature
     */
    public static String computeSignature(MessageDigest _msgDigest, MessageType msgType, String msgFormat,
            byte[] msgId, String userId, String putApplType, String putApplName, String putDate, String putTime) {
        _msgDigest.reset();
        if (msgType != null) {
            _msgDigest.update(String.valueOf(msgType.value()).getBytes());
        }
        if (msgFormat != null) {
            _msgDigest.update(msgFormat.trim().getBytes());
        }
        if (msgId != null) {
            _msgDigest.update(msgId);
        }
        if (userId != null) {
            _msgDigest.update(userId.trim().toLowerCase().getBytes());
        }
        if (putApplType != null) {
            _msgDigest.update(putApplType.trim().getBytes());
        }
        if (putApplName != null) {
            _msgDigest.update(putApplName.trim().getBytes());
        }
        if (putDate != null) {
            _msgDigest.update(putDate.trim().getBytes());
        }
        if (putTime != null) {
            _msgDigest.update(putTime.trim().getBytes());
        }
        return base64EncodeStr(_msgDigest.digest());
    }

    /**
     * Maps OpType enumeration constant from string or numeric value.
     *
     * @param opType
     *            object to be mapped to OpType enumeration constant
     * @return OpType mapping or {@code null} if mapping not found
     */
    public static OpType mapOpType(Object opType) {
        if (opType == null) {
            return null;
        }
        if (opType instanceof Number) {
            return mapOpType(((Number) opType).intValue());
        }
        return mapOpType(String.valueOf(opType));
    }

    private static OpType mapOpType(String opType) {
        try {
            return OpType.valueOf(opType.toUpperCase());
        } catch (IllegalArgumentException exc) {
            if (opType.equalsIgnoreCase("END") || opType.equalsIgnoreCase("FINISH") // NON-NLS
                    || opType.equalsIgnoreCase("DISCONNECT") || opType.equalsIgnoreCase("COMMIT") // NON-NLS
                    || opType.equalsIgnoreCase("BACK")) { // NON-NLS
                return OpType.STOP;
            }
            if (opType.equalsIgnoreCase("CONNECT") || opType.equalsIgnoreCase("BEGIN")) { // NON-NLS
                return OpType.START;
            }
            if (opType.equalsIgnoreCase("CALLBACK") || opType.equalsIgnoreCase("GET")) { // NON-NLS
                return OpType.RECEIVE;
            }
            if (opType.equalsIgnoreCase("PUT")) { // NON-NLS
                return OpType.SEND;
            }
            if (StringUtils.endsWithIgnoreCase(opType, "OPEN")) { // NON-NLS
                return OpType.OPEN;
            }
            if (StringUtils.endsWithIgnoreCase(opType, "CLOSE")) { // NON-NLS
                return OpType.CLOSE;
            }
        }

        return OpType.OTHER;
    }

    private static OpType mapOpType(int opType) {
        try {
            return OpType.valueOf(opType);
        } catch (IllegalArgumentException exc) {
        }

        return OpType.OTHER;
    }

    /**
     * Checks if provided string contains wildcard characters.
     *
     * @param str
     *            string to check if it contains wildcard characters
     * @return {@code true} if string contains wildcard characters
     */
    public static boolean isWildcardString(String str) {
        if (StringUtils.isNotEmpty(str)) {
            return str.contains("*") || str.contains("?"); // NON-NLS
        }
        return false;
    }

    /**
     * Transforms wildcard mask string to regex ready string.
     *
     * @param str
     *            wildcard string
     * @return regex ready string
     */
    public static String wildcardToRegex(String str) {
        return StringUtils.isEmpty(str) ? str : str.replace("?", ".?").replace("*", ".*?"); // NON-NLS
    }

    /**
     * Checks if string is wildcard mask string and if {@code true} then transforms it to regex ready string.
     *
     * @param str
     *            string to check and transform
     * @return regex ready string
     *
     * @see #wildcardToRegex(String)
     */
    public static String wildcardToRegex2(String str) {
        return isWildcardString(str) ? wildcardToRegex(str) : str;
    }

    /**
     * Counts text lines available in input.
     *
     * @param reader
     *            a {@link java.io.Reader} object to provide the underlying input stream
     * @return number of lines currently available in input
     * @throws java.io.IOException
     *             If an I/O error occurs
     * @deprecated use {@link #countLines(java.io.InputStream)} instead
     */
    @Deprecated
    public static int countLines(Reader reader) throws IOException {
        int lCount = 0;
        LineNumberReader lineReader = null;
        try {
            lineReader = new LineNumberReader(reader);
            lineReader.skip(Long.MAX_VALUE);
            // NOTE: Add 1 because line index starts at 0
            lCount = lineReader.getLineNumber() + 1;
        } finally {
            close(lineReader);
        }

        return lCount;
    }

    /**
     * Counts text lines available in input.
     *
     * @param is
     *            a {@link java.io.InputStream} object to provide the underlying file input stream
     * @return number of lines currently available in input
     * @throws java.io.IOException
     *             If an I/O error occurs
     */
    public static int countLines(InputStream is) throws IOException {
        InputStream bis = is instanceof BufferedInputStream ? (BufferedInputStream) is
                : new BufferedInputStream(is);
        try {
            byte[] c = new byte[1024];
            int count = 0;
            int readChars = 0;
            boolean endsWithoutNewLine = false;
            while ((readChars = bis.read(c)) != -1) {
                for (int i = 0; i < readChars; ++i) {
                    if (c[i] == '\n') {
                        ++count;
                    }
                }
                endsWithoutNewLine = (c[readChars - 1] != '\n');
            }
            if (endsWithoutNewLine) {
                ++count;
            }
            return count;
        } finally {
            close(bis);
        }
    }

    /**
     * Returns first readable file from {@code files} array which has last modification timestamp newer than
     * {@code lastModif}. If {@code lastModif} is {@code null}, then newest readable file is returned.
     *
     * @param files
     *            last modification timestamp ordered files array
     * @param lastModif
     *            last modification time to compare. If {@code null} - then newest file is returned
     * @return first file from array which has modification time later than {@code lastModif}, or {@code null} if
     *         {@code files} is empty or does not contain readable file with last modification time later than
     *         {@code lastModif}
     */
    public static File getFirstNewer(File[] files, Long lastModif) {
        File last = null;

        if (ArrayUtils.isNotEmpty(files)) {
            boolean changeDir = (files[0].lastModified() - files[files.length - 1].lastModified()) < 0;

            for (int i = changeDir ? files.length - 1 : 0; changeDir ? i >= 0
                    : i < files.length; i = changeDir ? i - 1 : i + 1) {
                File f = files[i];
                if (f.canRead()) {
                    if (lastModif == null) {
                        last = f;
                        break;
                    } else {
                        if (f.lastModified() > lastModif) {
                            last = f;
                        } else {
                            break;
                        }
                    }
                }
            }
        }

        return last;
    }

    /**
     * Deserializes JSON data object ({@link String}, {@link java.io.Reader}, {@link java.io.InputStream}) into map
     * structured data.
     *
     * @param jsonData
     *            JSON format data object
     * @param jsonAsLine
     *            flag indicating that complete JSON data package is single line
     * @return data map parsed from JSON data object
     * @throws com.google.gson.JsonSyntaxException
     *             if there was a problem reading from the Reader
     * @throws com.google.gson.JsonIOException
     *             if json is not a valid representation for an object of type
     * 
     * @see com.google.gson.Gson#fromJson(String, Class)
     * @see com.google.gson.Gson#fromJson(java.io.Reader, Class)
     */
    @SuppressWarnings("unchecked")
    public static Map<String, ?> fromJsonToMap(Object jsonData, boolean jsonAsLine) {
        Map<String, ?> map = new LinkedTreeMap<String, Object>();
        Gson gson = new Gson();

        if (jsonAsLine) {
            try {
                map = (Map<String, ?>) gson.fromJson(getStringLine(jsonData), map.getClass());
            } catch (IOException ioe) {
                throw new JsonIOException(ioe);
            }
        } else {
            if (jsonData instanceof String) {
                map = (Map<String, ?>) gson.fromJson((String) jsonData, map.getClass());
            } else if (jsonData instanceof byte[]) {
                map = (Map<String, ?>) gson.fromJson(getString((byte[]) jsonData), map.getClass());
            } else if (jsonData instanceof Reader) {
                map = (Map<String, ?>) gson.fromJson((Reader) jsonData, map.getClass());
            } else if (jsonData instanceof InputStream) {
                map = (Map<String, ?>) gson.fromJson(
                        new BufferedReader(new InputStreamReader((InputStream) jsonData)), map.getClass());
            }
        }

        return map;
    }

    /**
     * Returns string line read from data source. Data source object can be {@link String}, {@link java.io.Reader} or
     * {@link java.io.InputStream}.
     *
     * @param data
     *            data source object to read string line
     * @return string line read from data source
     * @throws java.io.IOException
     *             If an I/O error occurs while reading line
     *
     * @see java.io.BufferedReader#readLine()
     */
    public static String getStringLine(Object data) throws IOException {
        if (data == null) {
            return null;
        }

        BufferedReader rdr = null;
        boolean autoClose = false;
        if (data instanceof String) {
            rdr = new BufferedReader(new StringReader((String) data));
            autoClose = true;
        } else if (data instanceof byte[]) {
            rdr = new BufferedReader(new StringReader(getString((byte[]) data)));
            autoClose = true;
        } else if (data instanceof ByteBuffer) {
            ByteBuffer bb = (ByteBuffer) data;
            rdr = new BufferedReader(new StringReader(getString(bb.array())));
            autoClose = true;
        } else if (data instanceof BufferedReader) {
            rdr = (BufferedReader) data;
        } else if (data instanceof Reader) {
            rdr = new BufferedReader((Reader) data);
        } else if (data instanceof InputStream) {
            rdr = new BufferedReader(new InputStreamReader((InputStream) data));
        }

        try {
            return rdr == null ? null : rdr.readLine();
        } finally {
            if (autoClose) {
                close(rdr);
            }
        }
    }

    /**
     * Returns tag strings array retrieved from provided data object. Data object can be string (tags delimiter ','),
     * strings collection or strings array.
     *
     * @param tagsData
     *            tags data object
     * @return tag strings array, or {@code null} if arrays can't be made
     */
    @SuppressWarnings("unchecked")
    public static String[] getTags(Object tagsData) {
        if (tagsData instanceof byte[]) {
            return new String[] { encodeHex((byte[]) tagsData) };
        } else if (tagsData instanceof String) {
            return ((String) tagsData).split(TAG_DELIM);
        } else if (tagsData instanceof String[]) {
            return (String[]) tagsData;
        } else if (tagsData instanceof Collection) {
            Collection<String> tagsList = (Collection<String>) tagsData;
            if (!tagsList.isEmpty()) {
                String[] tags = new String[tagsList.size()];
                tags = tagsList.toArray(tags);

                return tags;
            }
        } else if (tagsData instanceof Object[]) {
            Object[] tagsArray = (Object[]) tagsData;
            if (tagsArray.length > 0) {
                String[] tags = new String[tagsArray.length];
                for (int i = 0; i < tagsArray.length; i++) {
                    tags[i] = toString(tagsArray[i]);
                }

                return tags;
            }
        }

        return null;
    }

    /**
     * Makes Hex string representation (starting '0x') of byte array.
     *
     * @param bytes
     *            byte array to represent as string
     * @return hex string representation of byte array
     *
     * @see #encodeHex(byte[])
     */
    public static String toHexString(byte[] bytes) {
        return HEX_PREFIX + encodeHex(bytes);
    }

    /**
     * Cleans raw activity data. If activity data is string, removes 'new line' symbols from it. Returns same object
     * otherwise.
     *
     * @param <T>
     *            type of raw activity data
     * @param activityData
     *            raw activity data
     * @return raw activity data without 'new line' symbols
     */
    @SuppressWarnings("unchecked")
    public static <T> T cleanActivityData(T activityData) {
        if (activityData instanceof String) {
            String ads = (String) activityData;

            return (T) LINE_ENDINGS_PATTERN.matcher(ads).replaceAll(""); // NON-NLS
        }

        return activityData;
    }

    /**
     * Makes a new {@link String} by decoding the specified array of bytes using the UTF-8 charset. If {@link String}
     * can't be constructed using UTF-8 charset, then the platform's default charset is used.
     *
     * @param strBytes
     *            The bytes to be decoded into characters
     * @return string constructed from specified byte array
     *
     * @see String#String(byte[], java.nio.charset.Charset)
     * @see String#String(byte[], String)
     * @see String#String(byte[])
     */
    public static String getString(byte[] strBytes) {
        try {
            return new String(strBytes, Charset.forName(UTF8));
        } catch (UnsupportedCharsetException uce) {
            try {
                return new String(strBytes, UTF8);
            } catch (UnsupportedEncodingException uee) {
                return new String(strBytes);
            }
        }
    }

    /**
     * Returns UUID found in provided string.
     *
     * @param str
     *            string to find UUID
     * @return found uuid
     */
    public static UUID findUUID(String str) {
        Matcher m = UUID_PATTERN.matcher(str);
        String fnUUID = null;
        if (m.find()) {
            fnUUID = m.group();
        }

        return StringUtils.isEmpty(fnUUID) ? null : UUID.fromString(fnUUID);
    }

    /**
     * Transforms (serializes) XML DOM document to string.
     *
     * @param doc
     *            document to transform to string
     * @return XML string representation of document
     * @throws javax.xml.transform.TransformerException
     *             If an exception occurs while transforming XML DOM document to string
     */
    public static String documentToString(Node doc) throws TransformerException {
        StringWriter sw = new StringWriter();
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); // NON-NLS
        transformer.setOutputProperty(OutputKeys.METHOD, "xml"); // NON-NLS
        transformer.setOutputProperty(OutputKeys.INDENT, "yes"); // NON-NLS
        transformer.setOutputProperty(OutputKeys.ENCODING, UTF8);

        transformer.transform(new DOMSource(doc), new StreamResult(sw));

        return sw.toString();
        // NOTE: if return as single line
        // return sw.toString().replaceAll("\n|\r", ""); // NON-NLS
    }

    /**
     * Checks equality of two double numbers with difference tolerance {@value #DEFAULT_EPSILON}.
     *
     * @param d1
     *            first double to compare
     * @param d2
     *            second double to compare
     * @return {@code true} if difference is less than epsilon, {@code false} - otherwise
     */
    public static boolean equals(double d1, double d2) {
        return equals(d1, d2, DEFAULT_EPSILON);
    }

    /**
     * Checks equality of two double numbers with given difference tolerance {@code epsilon}.
     *
     * @param d1
     *            first double to compare
     * @param d2
     *            second double to compare
     * @param epsilon
     *            value of difference tolerance
     * @return {@code true} if difference is less than epsilon, {@code false} - otherwise
     */
    public static boolean equals(double d1, double d2, double epsilon) {
        return Math.abs(d1 - d2) < epsilon;
    }

    /**
     * Close an socket without exceptions.
     * <p>
     * For Java 6 backward comparability.
     *
     * @param socket
     *            socket to close
     */
    public static void close(Socket socket) {
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException exc) {

            }
        }
    }

    /**
     * Close an server socket without exceptions.
     * <p>
     * For Java 6 backward comparability.
     *
     * @param socket
     *            server socket to close
     */
    public static void close(ServerSocket socket) {
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException exc) {

            }
        }
    }

    /**
     * Returns first non empty text string available to read from defined reader.
     *
     * @param reader
     *            reader to use for reading
     * @return non empty text string, or {@code null} if the end of the stream has been reached
     * @throws java.io.IOException
     *             If an I/O error occurs
     */
    public static String getNonEmptyLine(BufferedReader reader) throws IOException {
        String line;
        while ((line = reader.readLine()) != null) {
            if (line.trim().isEmpty()) {
                // empty line
            } else {
                break;
            }
        }

        return line;
    }

    /**
     * Reads text lines into one string from provided {@link java.io.InputStream}.
     *
     * @param is
     *            input stream to read
     * @param separateLines
     *            flag indicating whether to make string lines separated
     * @return string read from input stream
     *
     * @see #readInput(java.io.BufferedReader, boolean)
     */
    public static String readInput(InputStream is, boolean separateLines) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        String text = null;
        try {
            text = readInput(reader, separateLines);
        } catch (IOException exc) {
        } finally {
            close(reader);
        }

        return text;
    }

    /**
     * Reads text lines into one string from provided {@link java.io.BufferedReader}.
     *
     * @param reader
     *            reader to read string
     * @param separateLines
     *            flag indicating whether to make string lines separated
     * @return string read from reader
     */
    public static String readInput(BufferedReader reader, boolean separateLines) throws IOException {
        StringBuilder builder = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            builder.append(line);
            builder.append(separateLines ? NEW_LINE : " ");
        }

        return builder.toString();
    }

    /**
     * Gets a string representation of specified object for use in debugging, which includes the value of each object
     * field.
     *
     * @param obj
     *            object to get debugging info
     * @return debugging string representation
     */
    public static String getDebugString(Object obj) {
        if (obj == null) {
            return "null";
        }

        StringBuilder sb = new StringBuilder();
        Class<?> objClass = obj.getClass();
        sb.append(objClass.getSimpleName()).append('{');

        try {
            Field[] valueObjFields = objClass.getDeclaredFields();

            for (int i = 0; i < valueObjFields.length; i++) {
                String fieldName = valueObjFields[i].getName();
                valueObjFields[i].setAccessible(true);

                String valueStr = toString(valueObjFields[i].get(obj));

                sb.append(fieldName).append("=").append(sQuote(valueStr)) // NON-NLS
                        .append(i < valueObjFields.length - 1 ? ", " : ""); // NON-NLS
            }
        } catch (Exception exc) {
            sb.append(exc.toString());
        }

        sb.append('}');

        return sb.toString();
    }

    /**
     * Clears string builder content.
     *
     * @param sb
     *            string builder to clear content
     * @return empty string builder
     */
    public static StringBuilder clear(StringBuilder sb) {
        return sb == null ? null : sb.delete(0, sb.length());
    }

    /**
     * Clears string buffer content.
     *
     * @param sb
     *            string buffer to clear content
     * @return empty string buffer
     */
    public static StringBuffer clear(StringBuffer sb) {
        return sb == null ? null : sb.delete(0, sb.length());
    }

    /**
     * Checks if number object is {@code null} or has value equal to {@code 0}.
     *
     * @param number
     *            number object to check
     * @return {@code true} if number is {@code null} or number value is {@code 0}, {@code false} - otherwise
     */
    public static boolean isZero(Number number) {
        return number == null || number.intValue() == 0;
    }

    /**
     * Finds variable expressions like '${varName}' in provided array of strings and puts into collection.
     *
     * @param vars
     *            collection to add resolved variable expression
     * @param attrs
     *            array of {@link String}s to find variable expressions
     */
    public static void resolveCfgVariables(Collection<String> vars, String... attrs) {
        if (attrs != null) {
            for (String attr : attrs) {
                if (StringUtils.isNotEmpty(attr)) {
                    Matcher m = CFG_VAR_PATTERN.matcher(attr);
                    while (m.find()) {
                        vars.add(m.group());
                    }
                }
            }
        }
    }

    /**
     * Finds variable expressions like '${VarName}' in provided string and puts into collection.
     *
     * @param vars
     *            collection to add resolved variable expression
     * @param exprStr
     *            expression string
     */
    public static void resolveExpressionVariables(Collection<String> vars, String exprStr) {
        if (StringUtils.isNotEmpty(exprStr)) {
            Matcher m = EXPR_VAR_PATTERN.matcher(exprStr);
            while (m.find()) {
                vars.add(m.group(0));
            }
        }
    }

    /**
     * Makes {@link Object} type array from provided object instance.
     * <p>
     * If obj is {@code Object[]}, then simple casting is performed. If obj is {@link java.util.Collection}, then method
     * {@link java.util.Collection#toArray()} is invoked. In all other cases - new single item array is created.
     *
     * @param obj
     *            object instance to make an array
     * @return array made of provided object, or {@code null} if obj is {@code null}
     */
    public static Object[] makeArray(Object obj) {
        if (obj == null) {
            return null;
        }

        return obj instanceof Object[] ? (Object[]) obj
                : obj instanceof Collection ? ((Collection<?>) obj).toArray() : new Object[] { obj };
    }

    /**
     * Checks if provided object is {@link java.util.Collection} or {@code Object[]}.
     *
     * @param obj
     *            object to check
     * @return {@code true} if obj is {@link java.util.Collection} or {@code Object[]}, {@code false} - otherwise
     */
    public static boolean isCollection(Object obj) {
        return obj instanceof Object[] || obj instanceof Collection;
    }

    /**
     * Checks if provided class represents an array class or is implementation of {@link java.util.Collection}.
     *
     * @param cls
     *            class to check
     * @return {@code true} if cls is implementation of {@link java.util.Collection} or represents an array class,
     *         {@code false} - otherwise
     */
    public static boolean isCollectionType(Class<?> cls) {
        return cls != null && (cls.isArray() || Collection.class.isAssignableFrom(cls));
    }

    /**
     * Wraps provided object item seeking by index.
     * <p>
     * If obj is not {@link java.util.Collection} or {@code Object[]}, then same object is returned.
     * <p>
     * In case of {@link java.util.Collection} - it is transformed to {@code Object[]}.
     * <p>
     * When obj is {@code Object[]} - array item referenced by index is returned if {@code index < array.length}, first
     * array item if {@code array.length == 1} or {@code null} in all other cases.
     *
     * @param obj
     *            object instance containing item
     * @param index
     *            item index
     * @return item found, or {@code null} if obj is {@code null} or {@code index >= array.length}
     */
    public static Object getItem(Object obj, int index) {
        if (obj instanceof Collection) {
            obj = ((Collection<?>) obj).toArray();
        }

        if (obj instanceof Object[]) {
            Object[] array = (Object[]) obj;

            return index < array.length ? array[index] : array.length == 1 ? array[0] : null;
        }

        return obj;
    }

    /**
     * Returns the appropriate string representation for the specified object.
     *
     * @param value
     *            object to convert to string representation
     *
     * @return string representation of object
     */
    public static String toString(Object value) {
        if (value instanceof int[]) {
            return Arrays.toString((int[]) value);
        }
        if (value instanceof byte[]) {
            return getString((byte[]) value);
            // return Arrays.toString((byte[]) value);
        }
        if (value instanceof char[]) {
            return new String((char[]) value);
            // return Arrays.toString((char[]) value);
        }
        if (value instanceof long[]) {
            return Arrays.toString((long[]) value);
        }
        if (value instanceof float[]) {
            return Arrays.toString((float[]) value);
        }
        if (value instanceof short[]) {
            return Arrays.toString((short[]) value);
        }
        if (value instanceof double[]) {
            return Arrays.toString((double[]) value);
        }
        if (value instanceof boolean[]) {
            return Arrays.toString((boolean[]) value);
        }
        if (value instanceof Object[]) {
            return toStringDeep((Object[]) value);
        }
        if (value instanceof Collection) {
            Collection<?> c = (Collection<?>) value;
            return toString(c.toArray());
        }
        if (value instanceof Map) {
            Map<?, ?> m = (Map<?, ?>) value;
            return toString(m.entrySet());
        }

        return String.valueOf(value);
    }

    /**
     * Returns single object (first item) if list/array contains single item, makes an array from
     * {@link java.util.Collection}, or returns same value as parameter in all other cases.
     *
     * @param value
     *            object value to simplify
     * @return same value as parameter if it is not array or collection; first item of list/array if it contains single
     *         item; array of values if list/array contains more than one item; {@code null} if parameter object is
     *         {@code null} or list/array is empty
     */
    public static Object simplifyValue(Object value) {
        if (value instanceof Collection) {
            return simplifyValue((Collection<?>) value);
        }
        if (value instanceof Object[]) {
            return simplifyValue((Object[]) value);
        }

        return value;
    }

    private static Object simplifyValue(Collection<?> valuesList) {
        if (CollectionUtils.isEmpty(valuesList)) {
            return null;
        }

        return valuesList.size() == 1 ? valuesList.iterator().next() : valuesList.toArray();
    }

    private static Object simplifyValue(Object[] valuesArray) {
        if (ArrayUtils.isEmpty(valuesArray)) {
            return null;
        }

        return valuesArray.length == 1 ? valuesArray[0] : valuesArray;
    }

    /**
     * Returns the appropriate string representation for the specified array.
     *
     * @param a
     *            array to convert to string representation
     * @return string representation of array
     */
    public static String toStringDeep(Object[] a) {
        if (a == null) {
            return "null"; // NON-NLS
        }

        int iMax = a.length - 1;
        if (iMax == -1) {
            return "[]"; // NON-NLS
        }

        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0;; i++) {
            b.append(toString(a[i]));
            if (i == iMax) {
                return b.append(']').toString();
            }
            b.append(", "); // NON-NLS
        }
    }

    /**
     * Makes a HEX dump string representation of provided bytes array. Does all the same as
     * {@link #toHexDump(byte[], int)} setting {@code offset} parameter to {@code 0}.
     *
     * @param b
     *            bytes array make HEX dump
     * @return returns HEX dump representation of provided bytes array
     *
     * @see #toHexDump(byte[], int, int)
     */
    public static String toHexDump(byte[] b) {
        return toHexDump(b, 0);
    }

    /**
     * Makes a HEX dump string representation of provided bytes array. Does all the same as
     * {@link #toHexDump(byte[], int, int)} setting {@code len} parameter to {@code 0}.
     *
     * @param b
     *            bytes array make HEX dump
     * @param offset
     *            offset at which to start dumping bytes
     * @return returns HEX dump representation of provided bytes array
     *
     * @see #toHexDump(byte[], int, int)
     */
    public static String toHexDump(byte[] b, int offset) {
        return toHexDump(b, offset, 0);
    }

    /**
     * Makes a HEX dump string representation of provided bytes array.
     *
     * @param b
     *            bytes array make HEX dump
     * @param offset
     *            offset at which to start dumping bytes
     * @param len
     *            maximum number of bytes to dump
     * @return returns HEX dump representation of provided bytes array
     */
    public static String toHexDump(byte[] b, int offset, int len) {
        if (b == null) {
            return "<EMPTY>"; // NON-NLS
        }

        String hexStr;
        ByteArrayOutputStream bos = new ByteArrayOutputStream(b.length * 2);
        try {
            if (len > 0 && len < b.length) {
                byte[] bc = Arrays.copyOfRange(b, offset, offset + len);
                HexDump.dump(bc, 0, bos, offset);
            } else {
                HexDump.dump(b, 0, bos, offset);
            }
            hexStr = NEW_LINE + bos.toString(UTF8);
            bos.close();
        } catch (Exception exc) {
            hexStr = "HEX FAIL: " + exc.getLocalizedMessage(); // NON-NLS
        }

        return hexStr;
    }

    /**
     * Splits object identification path expression into path nodes array.
     *
     * @param path
     *            object identification path
     * @param nps
     *            path nodes separator
     * @return array of path nodes or {@code null} if path is empty
     */
    public static String[] getNodePath(String path, String nps) {
        if (StringUtils.isNotEmpty(path)) {
            return StringUtils.isEmpty(nps) ? new String[] { path } : path.split(Pattern.quote(nps));
        }

        return null;
    }

    /**
     * Checks if provided object is an array - of primitives or objects.
     *
     * @param obj
     *            object to check
     * @return {@code true} if obj is an array, {@code false} - otherwise
     */
    public static boolean isArray(Object obj) {
        return obj instanceof Object[] || (obj != null && obj.getClass().isArray());
    }

    /**
     * Sleeps current running thread and silently consumes thrown {@link InterruptedException}.
     *
     * @param millis
     *            the length of time to sleep in milliseconds
     *
     * @see Thread#sleep(long)
     */
    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException exc) {
        }
    }

    /**
     * Sleeps current running thread and silently consumes thrown {@link InterruptedException}.
     *
     * @param millis
     *            the length of time to sleep in milliseconds
     * @param nanos
     *            {@code 0-999999} additional nanoseconds to sleep
     *
     * @see Thread#sleep(long, int)
     */
    public static void sleep(long millis, int nanos) {
        try {
            Thread.sleep(millis, nanos);
        } catch (InterruptedException exc) {
        }
    }

    /**
     * Loads properties from file referenced by provided system property.
     *
     * @param propKey
     *            system property key referencing properties file path
     * @return properties loaded from file
     * @throws java.io.IOException
     *             if an error occurred when reading properties file
     *
     * @see #loadPropertiesFile(String)
     */
    public static Properties loadPropertiesFor(String propKey) throws IOException {
        String propFile = System.getProperty(propKey);

        return loadPropertiesFile(propFile);
    }

    /**
     * Loads properties from file.
     *
     * @param propFile
     *            properties file path
     * @return properties loaded from file
     * @throws java.io.IOException
     *             if an error occurred when reading properties file
     *
     * @see java.util.Properties#load(java.io.InputStream)
     */
    public static Properties loadPropertiesFile(String propFile) throws IOException {
        Properties fProps = new Properties();

        InputStream is = null;
        try {
            is = new FileInputStream(new File(propFile));
            fProps.load(is);
        } finally {
            close(is);
        }

        return fProps;
    }

    /**
     * Loads properties from resource with given name.
     *
     * @param name
     *            the resource name
     * @return properties loaded from resource
     * @throws java.io.IOException
     *             if an error occurred when reading properties file
     *
     * @see java.lang.ClassLoader#getResourceAsStream(String)
     * @see java.util.Properties#load(java.io.InputStream)
     */
    public static Properties loadPropertiesResource(String name) throws IOException {
        Properties rProps = new Properties();

        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        InputStream ins = loader.getResourceAsStream(name);

        try {
            rProps.load(ins);
        } finally {
            close(ins);
        }

        return rProps;
    }

    /**
     * Loads properties from all resource with given name.
     *
     * @param name
     *            the resource name
     * @return properties loaded from all found resources
     * @throws java.io.IOException
     *             if an error occurred when reading properties file
     *
     * @see java.lang.ClassLoader#getResources(String)
     * @see java.util.Properties#load(java.io.InputStream)
     */
    public static Properties loadPropertiesResources(String name) throws IOException {
        Properties rProps = new Properties();

        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> rEnum = loader.getResources(name);

        while (rEnum.hasMoreElements()) {
            InputStream ins = rEnum.nextElement().openStream();

            try {
                rProps.load(ins);
            } finally {
                close(ins);
            }
        }

        return rProps;
    }

    /**
     * Creates {@link java.io.BufferedReader} instance to read provided bytes data.
     *
     * @param data
     *            bytes data to read
     * @return bytes data reader
     */
    public static BufferedReader bytesReader(byte[] data) {
        return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(data)));
    }

    /**
     * Creates file according to provided file descriptor - file path or file URI.
     *
     * @param fileDescriptor
     *            string representing file path or URI
     * @return file instance
     * @throws IllegalArgumentException
     *             if {@code fileDescriptor} is {@code null} or empty
     * @throws java.net.URISyntaxException
     *             if {@code fileDescriptor} defines malformed URI
     */
    public static File createFile(String fileDescriptor) throws URISyntaxException {
        if (StringUtils.isEmpty(fileDescriptor)) {
            throw new IllegalArgumentException(StreamsResources.getString(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "Utils.empty.file.descriptor"));
        }

        if (fileDescriptor.startsWith("file:/")) { // NON-NLS
            URI fUri = new URI(fileDescriptor);
            return Paths.get(fUri).toFile();
        } else {
            return Paths.get(fileDescriptor).toFile();
        }
    }

    /**
     * Checks if provided numeric value match the mask.
     *
     * @param v
     *            numeric value
     * @param mask
     *            mask to check
     * @return {@code true} if value match the mask, {@code false} - otherwise
     */
    public static boolean matchMask(int v, int mask) {
        return (v & mask) == mask;
    }

    /**
     * Checks if provided numeric value match any bit of the mask.
     *
     * @param v
     *            numeric value
     * @param mask
     *            mask to check
     * @return {@code true} if value match any bit from the mask, {@code false} - otherwise
     */
    public static boolean matchAny(int v, int mask) {
        return (v & mask) != 0;
    }

    /**
     * Removes {@code null} elements from array.
     *
     * @param array
     *            array to cleanup
     * @param <T>
     *            type of array elements
     * @return new array instance without {@code null} elements
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] tidy(T[] array) {
        if (array == null) {
            return null;
        }

        List<T> oList = new ArrayList<>(array.length);
        for (T obj : array) {
            if (obj != null) {
                oList.add(obj);
            }
        }

        return (T[]) oList.toArray();
    }

    /**
     * Removes {@code null} elements from collection.
     *
     * @param coll
     *            collection to clean
     * @param <T>
     *            type of collection elements
     * @return new {@link java.util.List} instance without {@code null} elements
     */
    public static <T> List<T> tidy(Collection<T> coll) {
        if (coll == null) {
            return null;
        }

        List<T> oList = new ArrayList<>(coll.size());
        for (T obj : coll) {
            if (obj != null) {
                oList.add(obj);
            }
        }

        return oList;
    }

    /**
     * Casts provided number value to desired number type.
     *
     * @param num
     *            number value to cast
     * @param clazz
     *            number class to cast number to
     * @param <T>
     *            desired number type
     * @return number value cast to desired numeric type
     */
    @SuppressWarnings("unchecked")
    public static <T extends Number> T castNumber(Number num, Class<T> clazz) {
        Number cNum = 0;

        if (clazz.isAssignableFrom(Long.class)) {
            cNum = num.longValue();
        } else if (clazz.isAssignableFrom(Integer.class)) {
            cNum = num.intValue();
        } else if (clazz.isAssignableFrom(Byte.class)) {
            cNum = num.byteValue();
        } else if (clazz.isAssignableFrom(Float.class)) {
            cNum = num.floatValue();
        } else if (clazz.isAssignableFrom(Double.class)) {
            cNum = num.doubleValue();
        } else if (clazz.isAssignableFrom(Short.class)) {
            cNum = num.shortValue();
        }

        return (T) cNum;
    }

    /**
     * Sets system property value.
     * <p>
     * If property name is {@code null} - does nothing. If property value is {@code null} - removes property from system
     * properties list.
     *
     * @param pName
     *            the name of the system property
     * @param pValue
     *            the value of the system property
     * @return the previous string value of the system property, or {@code null} if name is {@code null} or there was no
     *         property with that name.
     */
    public static String setSystemProperty(String pName, String pValue) {
        if (StringUtils.isEmpty(pName)) {
            return null;
        }

        if (pValue != null) {
            return System.setProperty(pName, pValue);
        } else {
            return System.clearProperty(pName);
        }
    }

    /**
     * Returns enumeration entry based on entry name value ignoring case.
     *
     * @param enumClass
     *            enumeration instance class
     * @param name
     *            name of enumeration entry
     * @param <E>
     *            type of enumeration
     * @return enumeration object having provided name
     * @throws IllegalArgumentException
     *             if name is not a valid enumeration entry name or is {@code null}
     */
    public static <E extends Enum<E>> E valueOfIgnoreCase(Class<E> enumClass, String name)
            throws IllegalArgumentException {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException(
                    StreamsResources.getString(StreamsResources.RESOURCE_BUNDLE_NAME, "Utils.name.empty"));
        }

        E[] enumConstants = enumClass.getEnumConstants();

        for (E ec : enumConstants) {
            if (ec.name().equalsIgnoreCase(name)) {
                return ec;
            }
        }

        throw new IllegalArgumentException(StreamsResources.getStringFormatted(
                StreamsResources.RESOURCE_BUNDLE_NAME, "Utils.no.enum.constant", name, enumClass.getSimpleName()));
    }

    /**
     * Returns a quoted string, surrounded with single quote.
     *
     * @param str
     *            string handle
     * @return a quoted string, surrounded with single quote
     *
     * @see #surround(String, String)
     */
    public static String sQuote(String str) {
        return surround(str, "'"); // NON-NLS
    }

    /**
     * Returns a quoted object representation string, surrounded with single quote.
     *
     * @param obj
     *            object handle
     * @return a quoted object representation string, surrounded with single quote
     *
     * @see #sQuote(String)
     * @see #toString(Object)
     */
    public static String sQuote(Object obj) {
        return sQuote(toString(obj));
    }

    /**
     * Splits string contained values delimited using '|' delimiter into array of separate values.
     *
     * @param value
     *            string contained values to split
     * @return array of split string values
     */
    public static String[] splitValue(String value) {
        return StringUtils.isEmpty(value) ? new String[] { value } : value.split(VALUE_DELIM);
    }

    /**
     * Searches for files matching name pattern. Name pattern also may contain path of directory, where file search
     * should be performed, e.g., C:/Tomcat/logs/localhost_access_log.*.txt. If no path is defined (just file name
     * pattern) then files are searched in {@code System.getProperty("user.dir")}. Files array is ordered by file
     * modification timestamp in ascending order.
     *
     * @param namePattern
     *            name pattern to find files
     *
     * @return array of found files
     *
     * @see WildcardFileFilter#WildcardFileFilter(String)
     * @see File#listFiles(FilenameFilter)
     */
    public static File[] searchFiles(String namePattern) {
        File f = new File(namePattern);
        File dir = f.getAbsoluteFile().getParentFile();
        File[] activityFiles = dir.listFiles((FilenameFilter) new WildcardFileFilter(f.getName()));

        if (activityFiles != null) {
            Arrays.sort(activityFiles, new Comparator<File>() {
                @Override
                public int compare(File o1, File o2) {
                    long f1ct = o1.lastModified();
                    long f2ct = o2.lastModified();
                    // NOTE: we want files to be sorted oldest->newest (ASCENDING)
                    return f1ct < f2ct ? -1 : (f1ct == f2ct ? 0 : 1);
                }
            });

            return activityFiles;
        } else {
            return new File[0];
        }
    }

    /**
     * Returns list of files matching provided file name. If file name contains wildcard symbols, then
     * {@link #searchFiles(String)} is invoked.
     *
     * @param fileName
     *            file name pattern to list matching files
     * @return array of files matching file name
     *
     * @see #searchFiles(String)
     */
    public static File[] listFilesByName(String fileName) {
        if (isWildcardString(fileName)) {
            return searchFiles(fileName);
        } else {
            return new File[] { new File(fileName) };
        }
    }

    /**
     * Resolves map contained value by provided map keys path.
     * <p>
     * Path delimiter value is {@value com.jkoolcloud.tnt4j.streams.utils.StreamsConstants#DEFAULT_PATH_DELIM} and path
     * level value is {@code 0}.
     *
     * @param path
     *            map keys path string referencing wanted value
     * @param dataMap
     *            data map to get value from
     * @return path resolved map contained value
     *
     * @see #getNodePath(String, String)
     * @see #getMapValueByPath(String, Map, int)
     */
    public static Object getMapValueByPath(String path, Map<String, ?> dataMap) {
        return getMapValueByPath(path, dataMap, 0);
    }

    /**
     * Resolves map contained value by provided map keys path.
     * <p>
     * Path delimiter value is {@value com.jkoolcloud.tnt4j.streams.utils.StreamsConstants#DEFAULT_PATH_DELIM}.
     *
     * @param path
     *            map keys path string referencing wanted value
     * @param dataMap
     *            data map to get value from
     * @param level
     *            path level
     * @return path resolved map contained value
     *
     * @see #getNodePath(String, String)
     * @see #getMapValueByPath(String, String, Map, int)
     */
    public static Object getMapValueByPath(String path, Map<String, ?> dataMap, int level) {
        return getMapValueByPath(path, StreamsConstants.DEFAULT_PATH_DELIM, dataMap, level);
    }

    /**
     * Resolves map contained value by provided map keys path.
     * <p>
     * Path level value is {@code 0}.
     *
     * @param path
     *            map keys path string referencing wanted value
     * @param pathDelim
     *            path delimiter
     * @param dataMap
     *            data map to get value from
     * @return path resolved map contained value
     *
     * @see #getNodePath(String, String)
     * @see #getMapValueByPath(String, String, Map, int)
     */
    public static Object getMapValueByPath(String path, String pathDelim, Map<String, ?> dataMap) {
        return getMapValueByPath(path, pathDelim, dataMap, 0);
    }

    /**
     * Resolves map contained value by provided map keys path.
     *
     * @param path
     *            map keys path string referencing wanted value
     * @param pathDelim
     *            path delimiter
     * @param dataMap
     *            data map to get value from
     * @param level
     *            path level
     * @return path resolved map contained value
     *
     * @see #getNodePath(String, String)
     * @see #getMapValueByPath(String[], Map, int)
     */
    public static Object getMapValueByPath(String path, String pathDelim, Map<String, ?> dataMap, int level) {
        return getMapValueByPath(getNodePath(path, pathDelim), dataMap, level);
    }

    /**
     * Resolves map contained value by provided map keys path.
     * <p>
     * Path level value is {@code 0}.
     *
     * @param path
     *            map keys path tokens array referencing wanted value
     * @param dataMap
     *            data map to get value from
     * @return path resolved map contained value
     * 
     * @see #getMapValueByPath(String[], Map, int)
     */
    public static Object getMapValueByPath(String[] path, Map<String, ?> dataMap) {
        return getMapValueByPath(path, dataMap, 0);
    }

    /**
     * Resolves map contained value by provided map keys path.
     * 
     * @param path
     *            map keys path tokens array referencing wanted value
     * @param dataMap
     *            data map to get value from
     * @param level
     *            path level
     * @return path resolved map contained value
     */
    @SuppressWarnings("unchecked")
    public static Object getMapValueByPath(String[] path, Map<String, ?> dataMap, int level) {
        if (ArrayUtils.isEmpty(path) || dataMap == null) {
            return null;
        }

        if (StreamsConstants.MAP_NODE_TOKEN.equals(path[level])) {
            return dataMap;
        }

        Object val = dataMap.get(path[level]);

        if (level < path.length - 1 && val instanceof Map) {
            val = getMapValueByPath(path, (Map<String, ?>) val, ++level);
        } else if (level < path.length - 2 && val instanceof List) {
            try {
                int lii = Integer.parseInt(getItemIndexStr(path[level + 1]));
                val = getMapValueByPath(path, (Map<String, ?>) ((List<?>) val).get(lii), level + 2);
            } catch (NumberFormatException exc) {
            }
        }

        return val;
    }

    private static String getItemIndexStr(String indexToken) {
        return indexToken.replaceAll("\\D+", ""); // NON-NLS
    }
}