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

org.mockserver.collections.CaseInsensitiveRegexMultiMap Maven / Gradle / Ivy

package org.mockserver.collections;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.mockserver.log.model.LogEntry;
import org.mockserver.logging.MockServerLogger;
import org.mockserver.matchers.RegexStringMatcher;
import org.mockserver.model.KeyMatchStyle;
import org.mockserver.model.NottableString;
import org.mockserver.model.ObjectWithReflectiveEqualsHashCodeToString;

import java.util.*;
import java.util.stream.Collectors;

import static org.mockserver.collections.ImmutableEntry.entry;
import static org.mockserver.collections.ImmutableEntry.listsEqual;
import static org.mockserver.collections.SubSets.distinctSubSetsMap;
import static org.mockserver.model.NottableString.string;
import static org.mockserver.model.NottableString.strings;
import static org.slf4j.event.Level.TRACE;

/**
 * MultiMap that uses case insensitive regex expression matching for keys and values
 *
 * @author jamesdbloom
 */
@SuppressWarnings("NullableProblems")
public class CaseInsensitiveRegexMultiMap extends ObjectWithReflectiveEqualsHashCodeToString implements Map {
    private final CaseInsensitiveNottableRegexListHashMap backingMap;

    private final RegexStringMatcher regexStringMatcher;
    private final MockServerLogger mockServerLogger;
    private final KeyMatchStyle keyMatchStyle;
    private boolean noOptionals = true;

    public CaseInsensitiveRegexMultiMap(MockServerLogger mockServerLogger, boolean controlPlaneMatcher) {
        this(mockServerLogger, controlPlaneMatcher, KeyMatchStyle.SUB_SET);
    }

    public CaseInsensitiveRegexMultiMap(MockServerLogger mockServerLogger, boolean controlPlaneMatcher, KeyMatchStyle keyMatchStyle) {
        this.mockServerLogger = mockServerLogger;
        this.keyMatchStyle = keyMatchStyle;
        regexStringMatcher = new RegexStringMatcher(mockServerLogger, controlPlaneMatcher);
        backingMap = new CaseInsensitiveNottableRegexListHashMap(mockServerLogger, controlPlaneMatcher);
    }

    @VisibleForTesting
    public static CaseInsensitiveRegexMultiMap multiMap(boolean controlPlaneMatcher, KeyMatchStyle keyMatchStyle, String[]... keyAndValues) {
        CaseInsensitiveRegexMultiMap multiMap = new CaseInsensitiveRegexMultiMap(new MockServerLogger(), controlPlaneMatcher, keyMatchStyle);
        for (String[] keyAndValue : keyAndValues) {
            for (int i = 1; i < keyAndValue.length; i++) {
                multiMap.put(keyAndValue[0], keyAndValue[i]);
            }
        }
        return multiMap;
    }

    @VisibleForTesting
    public static CaseInsensitiveRegexMultiMap multiMap(boolean controlPlaneMatcher, KeyMatchStyle keyMatchStyle, NottableString[]... keyAndValues) {
        CaseInsensitiveRegexMultiMap multiMap = new CaseInsensitiveRegexMultiMap(new MockServerLogger(), controlPlaneMatcher, keyMatchStyle);
        for (NottableString[] keyAndValue : keyAndValues) {
            for (int i = 1; i < keyAndValue.length; i++) {
                multiMap.put(keyAndValue[0], keyAndValue[i]);
            }
        }
        return multiMap;
    }

    public boolean isNoOptionals() {
        return noOptionals;
    }

    public boolean containsAll(CaseInsensitiveRegexMultiMap matcher) {
        return containsAll(matcher, null);
    }

    public boolean containsAll(CaseInsensitiveRegexMultiMap matcher, String logCorrelationId) {
        switch (matcher.keyMatchStyle) {
            case SUB_SET: {
                List matchedEntries = entryList();
                Multimap> allMatchedSubSets
                    = distinctSubSetsMap(matchedEntries, ArrayListMultimap.create(), matchedEntries.size() - 1);

                if (MockServerLogger.isEnabled(TRACE)) {
                    mockServerLogger.logEvent(
                        new LogEntry()
                            .setLogLevel(TRACE)
                            .setCorrelationId(logCorrelationId)
                            .setMessageFormat("attempting to match subset from{}against multimap{}")
                            .setArguments(allMatchedSubSets, matcher.entryList())

                    );
                }

                if (isEmpty() && matcher.allKeysNotted()) {

                    return true;

                } else if (noOptionals && matcher.isNoOptionals()) {

                    // all non-optionals
                    List matcherEntries = matcher.entryList();
                    for (List matchedSubSet : allMatchedSubSets.get(matcherEntries.size())) {
                        if (listsEqual(matcherEntries, matchedSubSet)) {
                            if (MockServerLogger.isEnabled(TRACE)) {
                                mockServerLogger.logEvent(
                                    new LogEntry()
                                        .setLogLevel(TRACE)
                                        .setCorrelationId(logCorrelationId)
                                        .setMessageFormat("multimap{}containsAll subset{}in{}")
                                        .setArguments(this, matchedSubSet, matcherEntries)

                                );
                            }
                            return true;
                        }
                    }
                    if (MockServerLogger.isEnabled(TRACE)) {
                        mockServerLogger.logEvent(
                            new LogEntry()
                                .setLogLevel(TRACE)
                                .setCorrelationId(logCorrelationId)
                                .setMessageFormat("multimap{}containsAll found no subset equal to{}from{}")
                                .setArguments(this, matcherEntries, allMatchedSubSets)

                        );
                    }
                } else {

                    // some optionals exist
                    boolean result = false;

                    // first check non-optionals
                    List matcherEntriesWithoutOptionals = matcher.entryList().stream().filter(entry -> !entry.getKey().isOptional()).collect(Collectors.toList());
                    for (List matchedSubSet : allMatchedSubSets.get(matcherEntriesWithoutOptionals.size())) {
                        if (listsEqual(matcherEntriesWithoutOptionals, matchedSubSet)) {
                            if (MockServerLogger.isEnabled(TRACE)) {
                                mockServerLogger.logEvent(
                                    new LogEntry()
                                        .setLogLevel(TRACE)
                                        .setCorrelationId(logCorrelationId)
                                        .setMessageFormat("multimap{}containsAll subset of non-optionals{}in{}")
                                        .setArguments(this, matchedSubSet, matcherEntriesWithoutOptionals)

                                );
                            }
                            result = true;
                        }
                    }

                    // then check optionals
                    if (result) {
                        List optionalMatcherEntries = matcher.entryList().stream().filter(entry -> entry.getKey().isOptional()).collect(Collectors.toList());
                        if (!optionalMatcherEntries.isEmpty()) {
                            Set matchedSubSet = new HashSet<>();
                            for (ImmutableEntry optionalMatcherEntry : optionalMatcherEntries) {
                                List matchedValuesForKey = getAll(optionalMatcherEntry.getKey());
                                boolean matchesValue = false;
                                if (matchedValuesForKey.isEmpty()) {
                                    matchesValue = true;
                                }
                                for (NottableString matchedValue : matchedValuesForKey) {
                                    if (regexStringMatcher.matches(optionalMatcherEntry.getValue(), matchedValue, true)) {
                                        matchesValue = true;
                                        break;
                                    }
                                }
                                if (!matchesValue) {
                                    if (MockServerLogger.isEnabled(TRACE)) {
                                        mockServerLogger.logEvent(
                                            new LogEntry()
                                                .setLogLevel(TRACE)
                                                .setCorrelationId(logCorrelationId)
                                                .setMessageFormat("multimap{}matching by subset failed to match optional{}with any value from{}")
                                                .setArguments(this, optionalMatcherEntry, matchedValuesForKey)

                                        );
                                    }
                                    return false;
                                }
                            }
                            if (MockServerLogger.isEnabled(TRACE)) {
                                mockServerLogger.logEvent(
                                    new LogEntry()
                                        .setLogLevel(TRACE)
                                        .setCorrelationId(logCorrelationId)
                                        .setMessageFormat("multimap{}containsAll subset of optionals{}in{}")
                                        .setArguments(this, matchedSubSet, optionalMatcherEntries)

                                );
                            }
                            return true;
                        } else {
                            return true;
                        }
                    }

                    if (MockServerLogger.isEnabled(TRACE)) {
                        mockServerLogger.logEvent(
                            new LogEntry()
                                .setLogLevel(TRACE)
                                .setCorrelationId(logCorrelationId)
                                .setMessageFormat("multimap{}containsAll found no subset equal to{}from{}")
                                .setArguments(this, matcher.entryList(), matchedEntries)

                        );
                    }
                }
                return false;
            }
            case MATCHING_KEY: {
                for (NottableString matcherKey : matcher.keySet()) {
                    List matcherValuesForKey = matcher.getAll(matcherKey);
                    List matchedValuesForKey = getAll(matcherKey);
                    if (matchedValuesForKey.isEmpty() && !matcherKey.isOptional()) {
                        if (MockServerLogger.isEnabled(TRACE)) {
                            mockServerLogger.logEvent(
                                new LogEntry()
                                    .setLogLevel(TRACE)
                                    .setCorrelationId(logCorrelationId)
                                    .setMessageFormat("multimap{}containsAll matching by key found no matching values for{}")
                                    .setArguments(this, matcherKey)

                            );
                        }
                        return false;
                    }
                    for (NottableString matchedValue : matchedValuesForKey) {
                        boolean matchesValue = false;
                        for (NottableString matcherValue : matcherValuesForKey) {
                            if (regexStringMatcher.matches(matcherValue, matchedValue, true)) {
                                matchesValue = true;
                                break;
                            }
                        }
                        if (!matchesValue) {
                            if (MockServerLogger.isEnabled(TRACE)) {
                                mockServerLogger.logEvent(
                                    new LogEntry()
                                        .setLogLevel(TRACE)
                                        .setCorrelationId(logCorrelationId)
                                        .setMessageFormat("multimap{}containsAll matching by key found non-matching value{}for{}")
                                        .setArguments(this, matchedValue, matcherValuesForKey)

                                );
                            }
                            return false;
                        }
                    }
                }
                return true;
            }
        }
        return false;
    }

    public boolean allKeysNotted() {
        if (!isEmpty()) {
            for (NottableString key : keySet()) {
                if (!key.isNot()) {
                    return false;
                }
            }
        }
        return true;
    }

    public boolean allKeysOptional() {
        if (!isEmpty()) {
            for (NottableString key : keySet()) {
                if (!key.isOptional()) {
                    return false;
                }
            }
        }
        return true;
    }

    public synchronized boolean containsKeyValue(String key, String value) {
        return containsKeyValue(string(key), string(value));
    }

    public synchronized boolean containsKeyValue(NottableString key, NottableString value) {
        if (!isEmpty()) {
            for (NottableString valueToMatch : getAll(key)) {
                if (regexStringMatcher.matches(value, valueToMatch, true)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public synchronized boolean containsKey(Object key) {
        return backingMap.containsKey(key);
    }

    @Override
    public synchronized boolean containsValue(Object value) {
        if (!isEmpty()) {
            if (value instanceof NottableString) {
                for (NottableString key : backingMap.keySet()) {
                    for (List allKeyValues : backingMap.getAll(key)) {
                        for (NottableString keyValue : allKeyValues) {
                            if (regexStringMatcher.matches(keyValue, (NottableString) value, true)) {
                                return true;
                            }
                        }
                    }
                }
            } else if (value instanceof String) {
                return containsValue(string((String) value));
            }
        }
        return false;
    }

    @Override
    public synchronized NottableString get(Object key) {
        if (!isEmpty()) {
            if (key instanceof String) {
                return get(string((String) key));
            } else {
                List values = backingMap.get(key);
                if (values != null && values.size() > 0) {
                    return values.get(0);
                } else {
                    return null;
                }
            }
        } else {
            return null;
        }
    }

    public synchronized List getAll(String key) {
        return getAll(string(key));
    }

    public synchronized List getAll(NottableString key) {
        if (!isEmpty()) {
            List all = new ArrayList<>();
            for (List subList : backingMap.getAll(key)) {
                all.addAll(subList);
            }
            return all;
        } else {
            return Collections.emptyList();
        }
    }

    public synchronized NottableString put(String key, String value) {
        return put(string(key), string(value));
    }

    @Override
    public synchronized NottableString put(NottableString key, NottableString value) {
        if (key == null) {
            throw new IllegalArgumentException("key must not be null");
        }
        if (value == null) {
            throw new IllegalArgumentException("value must not be null");
        }
        List list = Collections.synchronizedList(new ArrayList<>());
        for (ImmutableEntry entry : entryList()) {
            // TODO(jamesdbloom) can this use of reflection equals be optimised
            if (EqualsBuilder.reflectionEquals(entry.getKey(), key, "key")) {
                if (key.isOptional()) {
                    throw new IllegalArgumentException("multiple values for optional key are not allowed, value \"" + entry.getValue() + "\" already exists for \"" + key + "\"");
                }
                list.add(entry.getValue());
            }
        }
        list.add(value);
        if (key.isOptional()) {
            noOptionals = false;
        }
        backingMap.put(key, list);
        return value;
    }

    public synchronized List put(String key, List values) {
        return put(string(key), strings(values));
    }

    public synchronized List put(NottableString key, List values) {
        if (containsKey(key)) {
            for (NottableString value : values) {
                put(key, value);
            }
        } else {
            backingMap.put(key, values);
        }
        return values;
    }

    @Override
    @SuppressWarnings("SuspiciousMethodCalls")
    public synchronized NottableString remove(Object key) {
        if (!isEmpty()) {
            if (key instanceof String) {
                return remove(string((String) key));
            } else {
                List values = backingMap.get(key);
                if (values != null && values.size() > 0) {
                    NottableString removed = values.remove(0);
                    if (values.size() == 0) {
                        backingMap.remove(key);
                    }
                    return removed;
                } else {
                    return null;
                }
            }
        } else {
            return null;
        }
    }

    public synchronized List removeAll(NottableString key) {
        return backingMap.remove(key);
    }

    @SuppressWarnings("SuspiciousMethodCalls")
    public synchronized List removeAll(String key) {
        return backingMap.remove(key);
    }

    @Override
    public synchronized void putAll(Map map) {
        for (Entry entry : map.entrySet()) {
            put(entry.getKey(), entry.getValue());
        }
    }

    @Override
    public synchronized void clear() {
        backingMap.clear();
    }

    @Override
    public synchronized Set keySet() {
        if (!isEmpty()) {
            return backingMap.keySet();
        } else {
            return Collections.emptySet();
        }
    }

    @Override
    public synchronized Collection values() {
        if (!isEmpty()) {
            Collection values = new ArrayList<>();
            for (List valuesForKey : backingMap.values()) {
                values.addAll(valuesForKey);
            }
            return values;
        } else {
            return Collections.emptyList();
        }
    }

    @Override
    public synchronized int size() {
        return backingMap.size();
    }

    @Override
    public synchronized boolean isEmpty() {
        return backingMap.isEmpty();
    }

    @Override
    public synchronized Set> entrySet() {
        if (!isEmpty()) {
            Set> entrySet = new LinkedHashSet<>();
            for (Entry> entry : backingMap.entrySet()) {
                for (NottableString value : entry.getValue()) {
                    entrySet.add(entry(regexStringMatcher, entry.getKey(), value));
                }
            }
            return entrySet;
        } else {
            return Collections.emptySet();
        }
    }

    public synchronized List entryList() {
        if (!isEmpty()) {
            List entrySet = new ArrayList<>();
            for (Entry> entry : backingMap.entrySet()) {
                for (NottableString value : entry.getValue()) {
                    entrySet.add(entry(regexStringMatcher, entry.getKey(), value));
                }
            }
            return entrySet;
        } else {
            return Collections.emptyList();
        }
    }
}







© 2015 - 2025 Weber Informatics LLC | Privacy Policy