package com.janosgyerik.utils.misc;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import static java.util.stream.Collectors.joining;

public final class StringUtils {

    static final String ERR_NULL_PARAM = "none of the parameters should be null";
    static final String ERR_SEARCHSTRINGS_REPLACEMENTS_LENGTH_MISMATCH =
            "there must be the same number of search strings and replacements";
    static final String ERR_NULL_OR_EMPTY_SEARCHSTRING = "there must be no null element or empty search string";
    static final String ERR_NULL_REPLACEMENT = "there must be no null element in replacements";
    static final String ERR_DUPLICATE_SEARCHSTRINGS = "search strings must be distinct";

    private StringUtils() {
        throw new AssertionError("utility class, forbidden constructor");
    }

    /**
     * Replace multiple search strings simultaneously
     *
     * @param text the source text
     * @param searchStrings search strings to replace
     * @param replacements texts to replace the corresponding search strings
     * @return new text with search strings replaced
     */
    public static String replace(String text, String[] searchStrings, String[] replacements) {
        validateParams(text, searchStrings, replacements);

        if (searchStrings.length == 0) {
            return text;
        }

        Map<String, String> searchStringsToReplacements = zipToMap(searchStrings, replacements);
        // note: using StringBuffer because of match.appendReplacement
        StringBuffer buffer = new StringBuffer();
        Matcher matcher = buildPattern(searchStrings).matcher(text);
        while (matcher.find()) {
            String pattern = matcher.group();
            String replacement = searchStringsToReplacements.get(pattern);
            matcher.appendReplacement(buffer, replacement);
        }
        matcher.appendTail(buffer);
        return buffer.toString();
    }

    private static Map<String, String> zipToMap(String[] searchStrings, String[] replacements) {
        Map<String, String> map = new HashMap<>();
        for (int i = 0; i < searchStrings.length; ++i) {
            map.put(searchStrings[i], replacements[i]);
        }
        return map;
    }

    private static Pattern buildPattern(String[] searchStrings) {
        return Pattern.compile(Stream.of(searchStrings).map(Pattern::quote).collect(joining("|")));
    }

    private static void validateParams(String text, String[] searchStrings, String[] replacements) {
        if (text == null || searchStrings == null || replacements == null) {
            throw new IllegalArgumentException(ERR_NULL_PARAM);
        }
        if (searchStrings.length != replacements.length) {
            throw new IllegalArgumentException(ERR_SEARCHSTRINGS_REPLACEMENTS_LENGTH_MISMATCH);
        }
        if (searchStrings.length == 0) {
            return;
        }
        if (anyNullOrEmpty(searchStrings)) {
            throw new IllegalArgumentException(ERR_NULL_OR_EMPTY_SEARCHSTRING);
        }
        if (anyNull(replacements)) {
            throw new IllegalArgumentException(ERR_NULL_REPLACEMENT);
        }
        if (containsDuplicates(searchStrings)) {
            throw new IllegalArgumentException(ERR_DUPLICATE_SEARCHSTRINGS);
        }
    }

    private static boolean anyNullOrEmpty(String[] strings) {
        return Stream.of(strings).anyMatch(x -> x == null || x.isEmpty());
    }

    private static boolean anyNull(String[] strings) {
        return Stream.of(strings).anyMatch(x -> x == null);
    }

    private static boolean containsDuplicates(String[] strings) {
        return Stream.of(strings).distinct().count() != strings.length;
    }
}

@@ -74,24 +74,50 @@
 a.util.Map;%0A
+import java.util.Objects;%0A
 import java.
@@ -450,20 +450,16 @@
         
-    
 %22there m
@@ -1014,16 +1014,25 @@
 am text 
+         
 the sour
@@ -1120,16 +1120,17 @@
 cements 
+ 
 texts to
@@ -2640,23 +2640,34 @@
 if (
-text == null %7C%7C
+anyNull(new Object%5B%5D%7Btext,
  sea
@@ -2676,27 +2676,17 @@
 hStrings
- == null %7C%7C
+,
  replace
@@ -2690,24 +2690,18 @@
 acements
- == null
+%7D)
 ) %7B%0A    
@@ -2939,79 +2939,8 @@
   %7D%0A
-        if (searchStrings.length == 0) %7B%0A            return;%0A        %7D%0A
     
@@ -3199,34 +3199,28 @@
     if (
-containsDuplicates
+!allDistinct
 (searchS
@@ -3503,30 +3503,27 @@
 anyNull(
-String%5B%5D strin
+Object%5B%5D ar
 gs) %7B%0A  
@@ -3537,37 +3537,34 @@
 eturn Stream.of(
-strin
+ar
 gs).anyMatch(x -
@@ -3560,27 +3560,28 @@
 nyMatch(
-x -%3E x == n
+Objects::isN
 ull);%0A  
@@ -3616,26 +3616,19 @@
 ean 
-containsDuplicates
+allDistinct
 (Str
@@ -3697,17 +3697,17 @@
 count() 
-!
+=
 = string

Drop unnecessary condition and generalize null check