package de.duehl.basics.collections;

/*
 * Copyright 2026 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import de.duehl.basics.io.FileHelper;
import de.duehl.basics.text.NumberString;
import de.duehl.basics.text.Text;

/**
 * Diese Klasse enthält Hilfsmethoden rund um Collections.
 *
 * @version 1.01     2026-06-30
 * @author Christian Dühl
 */

public class CollectionsHelper {

    private static final boolean DEBUG = false;


    /** Gibt einfach nur alle Elemente der Liste in Zeilen untereinander aus. */
    public static void printElements(List<?> list) {
        for (Object element : list) {
            System.out.println(element.toString());
        }
    }

    /** Gibt eine Liste von Strings schön aus. Mit Indices und in eckigen Klammern eingefasst. */
    public static void printListNice(List<?> list) {
        System.out.print(listListNice(list));
    }

    /**
     * Gibt eine Liste von Listen von Strings schön aus. Mit Indices und in eckigen Klammern
     * eingefasst.
     */
    public static <T> void printListOfListNice(List<List<T>> listOfList) {
        System.out.print(listListOfListNice(listOfList));
    }

    /**
     * Stellt eine Liste von Strings schön dar. Mit Indices beginnend bei 1 und in eckigen Klammern
     * eingefasst.
     */
    public static String listListNice(List<?> list) {
        return listListNice(list, 1);
    }

    /**
     * Stellt eine Liste schön dar. Mit Indices beginnend beim angegebenen Index und in eckigen
     * Klammern eingefasst.
     */
    public static String listListNice(List<?> list, int startIndex) {
        StringBuilder builder = new StringBuilder();

        for (int index = 0; index < list.size(); ++index) {
            String element = String.format("    %2d. [%s]", index + startIndex,
                    list.get(index).toString());
            builder.append(element).append(Text.LINE_BREAK);
        }

        return builder.toString();
    }

    /**
     * Stellt eine Listen von Listen schön dar. Mit Indices beginnend bei 1 und in eckigen Klammern
     * eingefasst.
     */
    public static <T> String listListOfListNice(List<List<T>> listOfList) {
        return listListOfListNice(listOfList, 1);
    }

    /**
     * Stellt eine Liste von Listen schön dar. Mit Indices beginnend beim angegebenen Index und in
     * eckigen Klammern eingefasst.
     *
     * Die inneren Listen beginnen aber immer mit eins.
     */
    public static <T> String listListOfListNice(List<List<T>> listOfList, int startIndex) {
        StringBuilder builder = new StringBuilder();

        for (int index = 0; index < listOfList.size(); ++index) {
            int outerIndexToShow = index + startIndex;
            String element = String.format("    %2d. Liste:", outerIndexToShow);
            builder.append(element).append(Text.LINE_BREAK);
            List<T> innerList = listOfList.get(index);
            for (int innerIndex = 0; innerIndex < innerList.size(); ++innerIndex) {
                int innerIndexToShow = innerIndex + 1;
                String innerElement = String.format("        %4d.%d. [%s]", outerIndexToShow,
                        innerIndexToShow, innerList.get(innerIndex).toString());
                builder.append(innerElement).append(Text.LINE_BREAK);
            }
        }

        return builder.toString();
    }

    /** Stellt eine Liste von Strings schön dar: Mit Indices. */
    public static String listListNiceWithoutBrackets(List<?> list) {
        StringBuilder builder = new StringBuilder();

        for (int index = 0; index < list.size(); ++index) {
            String element = String.format("    %2d. %s", index + 1, list.get(index).toString());
            builder.append(element).append(Text.LINE_BREAK);
        }

        return builder.toString();
    }

    /** Stellt eine Liste von Strings eingerückt dar. */
    public static String listListIndentedWithoutNumberAndBracket(List<?> list) {
        StringBuilder builder = new StringBuilder();

        for (int index = 0; index < list.size(); ++index) {
            builder
                    .append("    ")
                    .append(list.get(index).toString())
                    .append(Text.LINE_BREAK);
        }

        return builder.toString();
    }

    /** Stellt eine Liste von Strings dar. */
    public static String listListWithoutNumberAndBracket(List<?> list) {
        StringBuilder builder = new StringBuilder();

        for (int index = 0; index < list.size(); ++index) {
            builder
                    .append(list.get(index).toString())
                    .append(Text.LINE_BREAK);
        }

        return builder.toString();
    }

    /**
     * Stellt eine Liste von Strings schön dar. Mit Indices und in eckigen Klammern eingefasst.
     * Aber nur bis zur angegebenen Obergrenze.
     */
    public static String listList(List<?> list, int numberOfMaximalToShowEntries) {
        StringBuilder builder = new StringBuilder();

        for (int index = 0; index < list.size(); ++index) {
            if (index >= numberOfMaximalToShowEntries) {
                String element = "    ... [... weitere "
                        + (list.size() - numberOfMaximalToShowEntries)
                        + " Elemente ...]";
                builder.append(element).append(Text.LINE_BREAK);
                break;
            }
            String element = String.format("    %2d. [%s]", index + 1, list.get(index).toString());
            builder.append(element).append(Text.LINE_BREAK);
        }

        return builder.toString();
    }

    /**
     * Oft hat man als KeySet einer HashMap Integerwerte, die man dann auch gerne sortiert nach
     * ihrer Reihenfolge abarbeiten möchte. Diese Methode liefert die Schlüssel numerisch sortiert
     * zurück.
     *
     * @param keySet
     *            Unsortierte Menge an Integerwerten, wie sie map.keySet() zurückliefert.
     * @return Sortierte Liste mit den Schlüsseln.
     */
    public static List<Integer> getSortedIndices(Set<Integer> keySet) {
        List<Integer> ids = new ArrayList<>();
        ids.addAll(keySet);
        Collections.sort(ids);
        return ids;
    }

    /**
     * Oft hat man als KeySet einer HashMap Zeichenketten, die man dann auch gerne sortiert nach
     * ihrer Reihenfolge abarbeiten möchte. Diese Methode liefert die Schlüssel alphanumerisch
     * sortiert zurück.
     *
     * @param keySet
     *            Unsortierte Menge an Zeichenketten, wie sie map.keySet() zurückliefert.
     * @return Sortierte Liste mit den Schlüsseln.
     */
    public static List<String> getSortedStringIndices(Set<String> keySet) {
        List<String> strings = new ArrayList<>();
        strings.addAll(keySet);
        Collections.sort(strings);
        return strings;
    }

    /**
     * Oft hat man als KeySet einer HashMap Integerwerte, die man dann auch gerne sortiert nach
     * ihrer Reihenfolge abarbeiten möchte. Diese Methode liefert die Schlüssel numerisch sortiert
     * zurück.
     *
     * @param map
     *            Verzeichnis mit Integerwerten als Schlüsseln.
     * @return Sortierte Liste mit den Schlüsseln.
     */
    public static List<Integer> getSortedMapIndices(Map<Integer, ?> map) {
        Set<Integer> keySet = map.keySet();
        return getSortedIndices(keySet);
    }

    /**
     * Oft hat man als KeySet einer HashMap Zeichenketten, die man dann auch gerne sortiert nach
     * ihrer Reihenfolge abarbeiten möchte. Diese Methode liefert die Schlüssel alphanumerisch
     * sortiert zurück.
     *
     * @param map
     *            Verzeichnis mit Zeichenketten als Schlüsseln.
     * @return Sortierte Liste mit den Schlüsseln.
     */
    public static List<String> getSortedMapStringIndices(Map<String, ?> map) {
        Set<String> keySet = map.keySet();
        return getSortedStringIndices(keySet);
    }

    /**
     * Erzeugt eine neue Liste mit der umgedrehten Reihenfolge der Elemente.
     *
     * @param <T>
     *            Typ der Listenelemente.
     * @param list
     *            Umzudrehende Liste.
     * @return Neue, umgedrehte Liste.
     */
    public static <T> List<T> reverseList(List<T> list) {
        List<T> reverse = new ArrayList<>();
        reverse.addAll(list);
        Collections.reverse(reverse);
        return reverse;
    }

    /** Macht aus einer Reihe von Parametern eines Typs eine echte ArrayList. */
    @SafeVarargs
    // https://stackoverflow.com/questions/12462079/potential-heap-pollution-via-varargs-parameter
    public static <T> List<T> buildListFrom(T ... elements) {
        List<T> list = new ArrayList<>();

        for (T element : elements) {
            list.add(element);
        }

        return list;
    }

    /** Macht aus einer Liste und einer Reihe von Parametern eines Typs eine echte ArrayList. */
    @SafeVarargs
    // https://stackoverflow.com/questions/12462079/potential-heap-pollution-via-varargs-parameter
    public static <T> List<T> buildListFrom(List<T> oldList, T ... elements) {
        List<T> list = new ArrayList<>();

        list.addAll(oldList);
        list.addAll(buildListFrom(elements));

        return list;
    }

    /**
     * Macht aus einer Liste von String-Parametern eine echte ArrayList aus den kleingeschriebenen
     * Elementen.
     */
    public static List<String> buildLoweredListFrom(String ... texts) {
        return buildLoweredListFrom(arrayToList(texts));
    }

    /**
     * Macht aus einer Liste von String-Parametern eine echte ArrayList aus den kleingeschriebenen
     * Elementen.
     */
    public static List<String> buildLoweredListFrom(List<String> texts) {
        List<String> list = new ArrayList<>();

        for (String text : texts) {
            list.add(Text.toLowerCase(text));
        }

        return list;
    }

    /**
     * Macht aus einer Liste von String-Parametern eine echte ArrayList aus den großgeschriebenen
     * Elementen.
     */
    public static List<String> buildUpperedListFrom(String ... texts) {
        return buildUpperedListFrom(arrayToList(texts));
    }

    /**
     * Macht aus einer Liste von String-Parametern eine echte ArrayList aus den großgeschriebenen
     * Elementen.
     */
    public static List<String> buildUpperedListFrom(List<String> texts) {
        List<String> list = new ArrayList<>();

        for (String text : texts) {
            list.add(Text.toUpperCase(text));
        }

        return list;
    }

    /**
     * Macht aus einer Liste von String-Parametern eine echte ArrayList, die absteigend nach der
     * Länge der Texte sortiert ist.
     */
    public static List<String> buildSortByLengthDescandingListFrom(String ... texts) {
        List<String> list = buildListFrom(texts);
        return buildSortByLengthDescandingListFrom(list);
    }

    /**
     * Macht aus einer Liste von String-Parametern eine echte ArrayList, die absteigend nach der
     * Länge der Texte sortiert ist.
     */
    public static List<String> buildSortByLengthDescandingListFrom(List<String> list) {
        List<String> resultList = copyList(list);
        // TODO createSortStringListByLengthDescandingComparator() nutzen?!
        Collections.sort(resultList, new Comparator<String>() {
            @Override
            public int compare(String t1, String t2) {
                return t2.length() - t1.length();
            }
        });

        return resultList;
    }

    /**
     * Macht aus einer Liste von String-Parametern eine echte ArrayList, die aufsteigend nach der
     * Länge der Texte sortiert ist.
     */
    public static List<String> buildSortByLengthListFrom(String ... texts) {
        List<String> list = buildListFrom(texts);
        return buildSortByLengthListFrom(list);
    }

    /**
     * Macht aus einer Liste von String-Parametern eine echte ArrayList, die aufsteigend nach der
     * Länge der Texte sortiert ist.
     */
    public static List<String> buildSortByLengthListFrom(List<String> list) {
        List<String> resultList = copyList(list);
        Collections.sort(resultList, new Comparator<String>() {
            @Override
            public int compare(String t1, String t2) {
                return t1.length() - t2.length();
            }
        });

        return resultList;
    }

    /**
     * Macht aus den String-Parametern eine echte ArrayList, die aufsteigend nach den Texten
     * sortiert ist.
     */
    public static List<String> buildSortedListFrom(String ... texts) {
        List<String> list = buildListFrom(texts);

        Collections.sort(list);

        return list;
    }

    /**
     * Macht aus den String-Parametern eine echte ArrayList, die absteigend nach den Texten
     * sortiert ist.
     */
    public static List<String> buildDescandingSortedListFrom(String ... texts) {
        List<String> list = buildSortedListFrom(texts);

        Collections.reverse(list);

        return list;
    }

    /** Entfernt aus einer Liste von Strings alle leeren Strings. */
    public static void removeEmptyStringsFromList(List<String> list) {
        for (int index = list.size() - 1; index >= 0; --index) {
            String element = list.get(index);
            if (element.isEmpty()) {
                list.remove(index);
            }
        }
    }

    /**
     * Entfernt aus einer Liste von Strings alle Strings, welche die angegebenen Länge haben oder
     * kürzer sind.
     */
    public static void removeShortStringsFromList(List<String> list, int length) {
        for (int index = list.size() - 1; index >= 0; --index) {
            String element = list.get(index);
            if (element.length() <= length) {
                list.remove(index);
            }
        }
    }

    /**
     * Entfernt aus einer Liste von Strings alle leeren Strings und solche, die nur aus Whitespace
     * bestehen.
     */
    public static void removeEmptyAndOnlyWhitespaceStringsFromList(List<String> list) {
        for (int index = list.size() - 1; index >= 0; --index) {
            String element = list.get(index);
            if (element.trim().isEmpty()) {
                list.remove(index);
            }
        }
    }

    /**
     * Entfernt aus einer Liste von Strings alle leeren Strings die mit # (und gegebenenfalls
     * Leerzeichen davor) beginnen..
     */
    public static void removeCommentLinesFromList(List<String> list) {
        for (int index = list.size() - 1; index >= 0; --index) {
            String element = list.get(index);
            if (element.trim().startsWith("#")) {
                list.remove(index);
            }
        }
    }

    /**
     * Erzeugt eine dublettenfreie Liste.
     *
     * @param list
     *            Liste mit ggf. mehrfach vorkommenden Werten.
     * @return Dublettenfreie Liste.
     */
    public static List<String> removeMultipleValues(List<String> list) {
        List<String> singles = new ArrayList<>();

        for (String value : list) {
            if (!singles.contains(value)) {
                singles.add(value);
            }
        }

        return singles;
    }

    /** Entfernt das letzte Element der Liste, falls diese nicht leer ist. */
    public static void removeLastListEntry(List<?> list) {
        if (!list.isEmpty()) {
            int lastIndex = list.size() - 1;
            list.remove(lastIndex);
        }
    }

    /** Entfernt das letzte Element der Liste, falls diese nicht leer ist. */
    public static <T> T removeLastListEntryFromNotEmptyList(List<T> list) {
        if (list.isEmpty()) {
            throw new IllegalArgumentException("Die übergebene Liste ist leer!");
        }
        else {
            int lastIndex = list.size() - 1;
            return list.remove(lastIndex);
        }
    }

    /** Entfernt das letzte Element der Liste, falls diese nicht leer ist. */
    public static void removeFirstListEntry(List<?> list) {
        if (!list.isEmpty()) {
            int firstIndex = 0;
            list.remove(firstIndex);
        }
    }

    /** Entfernt das letzte Element der Liste, falls diese nicht leer ist. */
    public static <T> T removeFirstListEntryFromNotEmptyList(List<T> list) {
        if (list.isEmpty()) {
            throw new IllegalArgumentException("Die übergebene Liste ist leer!");
        }
        else {
            int firstIndex = 0;
            return list.remove(firstIndex);
        }
    }

    /** Erzeugt aus einem Array eine Liste von Objekten mit dem gleichen Inhalt. */
    public static <T> List<T> arrayToList(T[] array) {
        return new ArrayList<>(Arrays.asList(array));
    }

    /** Erzeugt aus einem Array von int-Werten eine Liste von Integern mit dem gleichen Inhalt. */
    public static List<Integer> arrayToList(int[] array) {
        List<Integer> list = new ArrayList<>();

        for (int number : array) {
            list.add(number);
        }

        return list;
    }

    /**
     * Erzeugt aus einem Array von double-Werten eine Liste von Double-Werten mit dem gleichen
     * Inhalt.
     */
    public static List<Double> arrayToList(double[] array) {
        List<Double> list = new ArrayList<>();

        for (double value : array) {
            list.add(value);
        }

        return list;
    }

    /** Erzeugt aus einem String-Array eine Liste von Strings mit dem gleichen Inhalt. */
    public static List<String> stringArrayToList(String[] array) {
        return new ArrayList<>(Arrays.asList(array));
    }

    /** Erzeugt aus der übergebenen Liste von Strings ein String-Array mit dem gleichen Inhalt. */
    public static String[] stringListToArray(List<String> list) {
        String[] array = new String[list.size()];
        array = list.toArray(array);
        /* -> http://stackoverflow.com/questions/5374311/convert-arrayliststring-to-string-array */
        return array;
    }

    /** Gibt den Inhalt eines Arrays in einem einzeiligen String aus. */
    public static String stringArrayToString(String[] array) {
        List<String> list = new ArrayList<>(Arrays.asList(array));
        return Text.join(", ", list);
    }

    /** Erzeugt aus einem Vector eine Liste von Objekten mit dem gleichen Inhalt. */
    public static <T> List<T> vectorToList(Vector<T> vector) {
        List<T> list = new ArrayList<>();
        for (T element : vector) {
            list.add(element);
        }
        return list;
    }

    /** Entfernt leere Strings zu Beginn einer Liste von Strings. */
    public static void removeEmptyStringsAtFrontOfList(List<String> list) {
        int indexOfFirstNotEmptyElement = determineIndexOfFirstNotEmptyElement(list);
        for (int index = list.size() - 1; index >= 0; --index) {
            if (index < indexOfFirstNotEmptyElement) {
                list.remove(index);
            }
        }
    }

    /**
     * Ermittelt den Index des ersten nicht leeren Elements der Liste. Sind alle Elemente leer, so
     * wird die Anzahl der Elemente der Liste zurückgegeben. Ist das erste Element vorhanden und
     * nicht leer, so wird -1 zurückgegeben.
     */
    static int determineIndexOfFirstNotEmptyElement(List<String> list) {
        for (int index = 0; index < list.size(); ++index) {
            String element = list.get(index);
            if (!element.isEmpty()) {
                return index;
            }
        }
        return list.size();
    }

    /** Entfernt leere Strings am Ende einer Liste von Strings. */
    public static void removeEmptyStringsAtEndOfList(List<String> list) {
        int indexOfLastNotEmptyElement = determineIndexOfLastNotEmptyElement(list);
        for (int index = list.size() - 1; index >= 0; --index) {
            if (index > indexOfLastNotEmptyElement) {
                list.remove(index);
            }
        }
    }

    /**
     * Ermittelt den Index des letzten nicht leeren Elements der Liste. Sind alle Elemente leer, so
     * wird -1 zurückgegeben. Ist das letzte Element vorhanden und nicht leer, so wird die Anzahl
     * der Elemente der Liste zurückgegeben.
     */
    static int determineIndexOfLastNotEmptyElement(List<String> list) {
        for (int index = list.size() - 1; index >= 0; --index) {
            String element = list.get(index);
            if (!element.isEmpty()) {
                return index;
            }
        }
        return -1;
    }

    /**
     * Sortiert eine Liste von Strings absteigend nach der Länge ihrer Elemente. Bei gleichlangen
     * Elementen wird alphabetisch sortiert.
     */
    public static void sortStringListByLengthDescanding(List<String> list) {
        Collections.sort(list, createSortStringListByLengthDescandingComparator());
    }

    private static Comparator<String> createSortStringListByLengthDescandingComparator() {
        return new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                int length1 = o1.length();
                int length2 = o2.length();

                int cmp = length2 - length1;
                if (cmp != 0) {
                    return cmp;
                }
                else {
                    return o1.compareTo(o2);
                }
            }
        };
    }

    /** Stellt fest, ob die übergebene Menge disjunkt / unikat ist. */
    public static boolean isDisjunct(List<?> list) {
        for (int i = 0; i < list.size() - 1; ++i) {
            for (int j = i + 1; j < list.size(); ++j) {
                if (list.get(i).equals(list.get(j))) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Gibt die Liste aller nicht disjunkten Elemente zurück. Ist de übergebene Menge disjunkt,
     * wird eine leere Liste zurückgegeben.
     */
    public static List<String> getNotDisjunktTexts(List<String> list) {
        List<String> notDisjunctTexts = new ArrayList<>();

        for (int i = 0; i < list.size() - 1; ++i) {
            for (int j = i + 1; j < list.size(); ++j) {
                String text1 = list.get(i);
                String text2 = list.get(j);
                if (text1.equals(text2) && !notDisjunctTexts.contains(text1)) {
                    notDisjunctTexts.add(text1);
                }
            }
        }

        return notDisjunctTexts;
    }

    /** Erzeugt aus einer Liste mit Strings eine unikate Liste mit disjunkten Inhalten. */
    public static List<String> createDisjunctLowerCaseList(List<String> list) {
        return createDisjunctList(toLowerCase(list));
        // Selbst bei unikaten Listen können durch toLowerCase welche gleich werden.
    }

    /** Erzeugt aus einer Liste mit Strings eine unikate Liste mit disjunkten Inhalten. */
    public static <Element> List<Element> createDisjunctList(List<Element> list) {
        List<Element> disjunctList = new ArrayList<>();

        for (Element value : list) {
            if (!disjunctList.contains(value)) {
                disjunctList.add(value);
            }
        }

        return disjunctList;
    }

    /**
     * Macht aus einer Liste mit Strings eine unikate Liste mit disjunkten Inhalten, indem in der
     * originalen Liste die Dubletten zu weiter vorn vorhandenen Einträgen entfernt werden.
     *
     * Wichtig dabei ist, dass equals für die Elemente der Liste sinnvoll gebildet wurde.
     */
    public static <Element> void makeListDisjunct(List<Element> list) {
        for (int index = list.size() - 1; index > 0; --index) {
            Element value = list.get(index);
            for (int indexBefore = 0; indexBefore < index; ++indexBefore) {
                Element valueBefore = list.get(indexBefore);
                if (value.equals(valueBefore)) {
                    list.remove(index);
                    break;
                }
            }
        }
    }

    /** Erzeugt eine neue Liste mit allen Elemente der Eingangs-Liste in Kleinbuchstaben. */
    public static List<String> toLowerCase(List<String> list) {
        List<String> loweredList = new ArrayList<>();
        for (String text : list) {
            loweredList.add(Text.toLowerCase(text));
        }
        return loweredList;
    }

    /** Erzeugt eine neue Liste mit allen Elemente der Eingangs-Liste in Großbuchstaben. */
    public static List<String> toUpperCase(List<String> list) {
        List<String> upperedList = new ArrayList<>();
        for (String text : list) {
            upperedList.add(Text.toUpperCase(text));
        }
        return upperedList;
    }

    /**
     * Erzeugt eine neue Liste, in der die Elemente der übergebenen Liste mit den angegebenen
     * vorher und hinterher einzufügenden Texten umgeben sind.
     *
     * @param list
     *            Liste, deren Elemente dekoriert werden sollen.
     * @param before
     *            Text der jedem Element der Liste vorzustellen ist.
     * @param after
     *            Text der an jedes Element der Liste anzuhängen ist.
     * @return Liste mit den dekorierten Elementen.
     */
    public static List<String> surroundElements(List<String> list, String before,
            String after) {
        List<String> surroundedList = new ArrayList<>();
        for (String element : list) {
            String surroundedElement = before + element + after;
            surroundedList.add(surroundedElement);
        }

        return surroundedList;
    }

    /**
     * Behält von der übergebenen Liste nur so viele Zeilen vom Anfang, wie angegeben.
     *
     * @param list
     *            Liste
     * @param numberOfLinesToKeep
     *            Anzahl zu behaltender Zeilen.
     * @return Liste mit den Anfangszeilen.
     */
    public static List<String> keepOnlyFirstLines(List<String> list, int numberOfLinesToKeep) {
        if (list.size() <= numberOfLinesToKeep) {
            return list;
        }
        else {
            List<String> firstLines = new ArrayList<>();
            for (int index = 0; index < numberOfLinesToKeep; ++index) {
                firstLines.add(list.get(index));
            }
            return firstLines;
        }
    }

    /** Erzeugt aus einem String und einen StringArray ein neues StringArray. */
    public static String[] appendInfrontOfArray(String firstValue, String[] otherValues) {
        String[] newArray = new String[otherValues.length + 1];
        newArray[0] = firstValue;
        for (int index = 0; index < otherValues.length; ++index) {
            newArray[index + 1] = otherValues[index];
        }
        return newArray;
    }

    /** Erzeugt aus einem String und einen StringArray ein neues StringArray. */
    public static String[] appendAtEndOfArray(String[] otherValues, String lastValue) {
        String[] newArray = new String[otherValues.length + 1];
        for (int index = 0; index < otherValues.length; ++index) {
            newArray[index] = otherValues[index];
        }
        newArray[otherValues.length] = lastValue;
        return newArray;
    }

    /**
     * Erzeugt einen Ausschnitt der Liste, beginnend mit der Zeile nach der ersten Zeile, welche
     * start enthält und endend vor der nächsten Zeile, welche end enthält.
     *
     * @param lines
     *            Liste von Strings.
     * @param start
     *            Begriff vor der ersten Zeile des Ausschnitts.
     * @param end
     *            Begriff nach dem Ausschnitt.
     * @return Teil der Liste.
     */
    public static List<String> getSection(List<String> lines, String start, String end) {
        List<String> section = new ArrayList<>();
        boolean startFound = false;

        for (String line : lines) {
            if (startFound) {
                if (line.contains(end)) {
                    return section;
                }
                else {
                    section.add(line);
                }
            }
            else {
                if (line.contains(start)) {
                    startFound = true;
                }
            }
        }

        if (!startFound) {
            throw new RuntimeException("Der Startbegriff '" + start
                    + "' wurde nicht in der Liste gefunden.");
        }
        else {
            throw new RuntimeException("Der Endbegriff '" + end
                    + "' wurde nach dem Startbegriff nicht in der Liste gefunden.");
        }
    }

    /**
     * Entfernt den ersten Ausschnitt der Liste, beginnend mit der Zeile nach der ersten Zeile,
     * welche start enthält und endend vor der nächsten Zeile, welche end enthält.
     *
     * @param lines
     *            Liste von Strings.
     * @param start
     *            Begriff vor der ersten Zeile des Ausschnitts.
     * @param end
     *            Begriff nach dem Ausschnitt.
     * @return Liste ohne den Teil.
     */
    public static List<String> removeSectionExcludingStartAndEnd(List<String> lines, String start,
            String end) {
        List<String> listWithoutSection = new ArrayList<>();
        boolean startFound = false;
        boolean endFound = false;

        for (String line : lines) {
            if (startFound && !endFound) {
                if (line.contains(end)) {
                    endFound = true;
                    listWithoutSection.add(line);
                }
            }
            else if (endFound) {
                listWithoutSection.add(line);
            }
            else {
                if (line.contains(start)) {
                    startFound = true;
                }
                listWithoutSection.add(line);
            }
        }

        if (!startFound) {
            throw new RuntimeException("Der Startbegriff '" + start
                    + "' wurde nicht in der Liste gefunden.");
        }
        else if (!endFound) {
            throw new RuntimeException("Der Endbegriff '" + end
                    + "' wurde nach dem Startbegriff nicht in der Liste gefunden.");
        }
        else {
            return listWithoutSection;
        }
    }

    /**
     * Entfernt den ersten Ausschnitt der Liste, beginnend mit der ersten Zeile, welche start
     * enthält und endend mit der Zeile, welche end enthält.
     *
     * @param lines
     *            Liste von Strings.
     * @param start
     *            Begriff in der ersten Zeile des Ausschnitts.
     * @param end
     *            Begriff in der letzten Zeile des Ausschnitts.
     * @return Liste ohne den Teil.
     */
    public static List<String> removeSectionIncludingStartAndEnd(List<String> lines, String start,
            String end) {
        List<String> listWithoutSection = new ArrayList<>();
        boolean startFound = false;
        boolean endFound = false;

        for (String line : lines) {
            if (startFound && !endFound) {
                if (line.contains(end)) {
                    endFound = true;
                }
            }
            else {
                if (!endFound && line.contains(start)) {
                    startFound = true;
                }
                else {
                    listWithoutSection.add(line);
                }
            }
        }

        if (!startFound) {
            throw new RuntimeException("Der Startbegriff '" + start
                    + "' wurde nicht in der Liste gefunden.");
        }
        else if (!endFound) {
            throw new RuntimeException("Der Endbegriff '" + end
                    + "' wurde nach dem Startbegriff nicht in der Liste gefunden.");
        }
        else {
            return listWithoutSection;
        }
    }

    /** Stellt fest, ob der Suchbegriff in einer der Elemente der Liste vorkommt. */
    public static boolean anyElementContains(List<String> list, String searchWord) {
        for (String line : list) {
            if (line.contains(searchWord)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Stellt fest, ob der irgendeiner der Suchbegriffe in einem der Elemente der Liste vorkommt.
     *
     * @param list
     *            Die Liste, die nach den Suchbegriffen durchsucht wird.
     * @param searchWords
     *            Die Suchbegriffe, nach denen gesucht wird.
     */
    public static boolean anyElementContainsAnySearchWord(List<String> list,
            List<String> searchWords) {
        for (String line : list) {
            if (containesAnyListElement(line, searchWords)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Stellt fest, ob der irgendeiner der Suchbegriffe in einem der Elemente der Liste vorkommt.
     *
     * @param list
     *            Die Liste, die nach den Suchbegriffen durchsucht wird.
     * @param searchWords
     *            Die Suchbegriffe, nach denen gesucht wird.
     */
    public static boolean anyElementEqualsAnySearchWord(List<String> list,
            List<String> searchWords) {
        for (String line : list) {
            if (inputEqualsAnyListElement(line, searchWords)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Sucht alle Vorkommen eines Suchbegriffs in einer Liste von Strings.
     *
     * @param searchText
     *            Text, nachdem gesucht wird.
     * @param list
     *            Liste von Strings, die durchsucht wird.
     * @return Liste mit allen Indices des Suchtextes in der Liste, ggf. leer.
     */
    public static <T> List<Integer> findAllIndces(T searchText, List<T> list) {
        List<Integer> indices = new ArrayList<>();

        for (int index = 0; index < list.size(); ++index) {
            T listElement = list.get(index);
            if (listElement.equals(searchText)) {
                indices.add(index);
            }
        }

        return indices;
    }

    /** Konkatiniert beide Listen zu einer. */
    public static List<String> joinStringLists(List<String> list1, List<String> list2) {
        return joinLists(list1, list2);
        //List<String> totalList = new ArrayList<>();
        //totalList.addAll(list1);
        //totalList.addAll(list2);
        //return totalList;
    }

    /** Konkatiniert beliebig viele Listen des gleichen Typs. */
    @SafeVarargs
    public static <T> List<T> joinLists(List<T> ... lists) {
        List<T> joinedList = new ArrayList<>();

        for (List<T> list : lists) {
            joinedList.addAll(list);
        }

        return joinedList;
    }

    /**
     * Konkatiniert beliebig viele Listen des gleichen Typs, doppelte Elemente werden nur einmal
     * behalten.
     */
    @SafeVarargs
    public static <T> List<T> joinListsDisjunct(List<T> ... lists) {
        List<T> joinedList = new ArrayList<>();

        for (List<T> list : lists) {
            for (T t : list) {
                if (!joinedList.contains(t)) {
                    joinedList.add(t);
                }
            }
        }

        return joinedList;
    }

    /** Fertigt eine flache Kopie der Liste an. */
    public static <Element> List<Element> copyList(List<Element> list) {
        List<Element> copyiedList = new ArrayList<>();

        copyiedList.addAll(list);

        return copyiedList;
    }

    /** Trimmt alle Elemente der Liste. */
    public static List<String> trimList(List<String> texts) {
        List<String> trimmed = new ArrayList<>();

        for (String text : texts) {
            trimmed.add(text.trim());
        }

        return trimmed;
    }

    /**
     * Entfernt in allen Elementen der Liste Leerzeichen zu Beginn und am Ende und macht aus allen
     * mehrfachen Leerzeichen ein einzelnes. *
     */
    public static List<String> trimAndCompactSpaces(List<String> texts) {
        List<String> cleaned = new ArrayList<>();

        for (String text : texts) {
            cleaned.add(Text.trimAndCompactSpaces(text));
        }

        return cleaned;
    }

    /**
     * Prüft, ob die übergebene Liste mit den Teilen der Start-Teil-Liste beginnt.
     *
     * @param longList
     *            Zu prüfende Liste.
     * @param startingListParts
     *            Teile mit denen sie Anfangen soll.
     */
    public static <T> boolean listStartsWith(List<T> longList, List<T> startingListParts) {
        if (longList.size() < startingListParts.size()) {
            return false;
        }

        for (int index = 0; index < startingListParts.size(); ++index) {
            T partFromList = longList.get(index);
            T partFromStartingList = startingListParts.get(index);
            if (!partFromList.equals(partFromStartingList)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Prüft ob der übergebene input einem der Elemente aus der übergebenen Liste gleicht.
     *
     * @param input
     *            Zu überprüfende Eingabe.
     * @param list
     *            Mögliche Werte, denen die Eingabe gleichen darf.
     * @return Wahrheitswert.
     */
    public static boolean inputEqualsAnyListElement(String input, List<String> list) {
        for (String element : list) {
            if (input.equals(element)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Prüft ob der übergebene input mit einem der Elemente aus der übergebenen Liste beginnt.
     *
     * @param input
     *            Zu überprüfende Eingabe.
     * @param list
     *            Mögliche Anfänge, mit der die Eingabe beginnen darf.
     * @return Wahrheitswert.
     */
    public static boolean inputStartsWithAnyListElement(String input, List<String> list) {
        for (String element : list) {
            if (input.startsWith(element)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Prüft ob der übergebene Input auf eines der Elemente aus der übergebenen Liste endet.
     *
     * @param input
     *            Zu überprüfende Eingabe.
     * @param list
     *            Mögliche Enden, auf die die Eingabe enden darf.
     * @return Wahrheitswert.
     */
    public static boolean inputEndsWithAnyListElement(String input, List<String> list) {
        for (String element : list) {
            if (input.endsWith(element)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Gibt das erste Element zurück, auf welches der übergebene Input endet.
     *
     * Endet er auf keines der Elemente, wird der leere String zurückgegeben.
     *
     * @param input
     *            Zu überprüfende Eingabe.
     * @param list
     *            Mögliche Enden, auf die die Eingabe enden darf.
     * @return Wahrheitswert.
     */
    public static String getElementOnWichTheInputEnds(String input, List<String> list) {
        for (String element : list) {
            if (input.endsWith(element)) {
                return element;
            }
        }

        return "";
    }

    /** Gibt zu einem String eine Liste mit seinen Buchstaben (Zeichen) zurück. */
    public static List<String> splitStringIntoLetters(String text) {
        List<String> splitted = new ArrayList<>();

        for (int index = 0; index < text.length(); ++index) {
            String letter = text.substring(index, index + 1);
            splitted.add(letter);
        }

        return splitted;
    }

    /** Ersetzt in allen Zeilen der Liste von Strings Begriffe. */
    public static List<String> replace(List<String> lines, String toReplace, String replacement) {
        List<String> replacedLines = new ArrayList<>();

        for (String line : lines) {
            String replacedLine = line.replace(toReplace, replacement);
            replacedLines.add(replacedLine);
        }

        return replacedLines;
    }

    /** Ersetzt in allen Zeilen der Liste von Strings Begriffe mit Hilfe Regulärer Ausdrücke. */
    public static List<String> replaceAll(List<String> lines, String toReplace, String replacement) {
        List<String> replacedLines = new ArrayList<>();

        for (String line : lines) {
            String replacedLine = line.replaceAll(toReplace, replacement);
            replacedLines.add(replacedLine);
        }

        return replacedLines;
    }

    /**
     * Wendet auf eine Liste von Objekten eine Methode an, die ein Ergebnis in String-Form erzeugt
     * und gibt die Liste dieser ERgebnisse zurück.
     *
     * @param <T>
     *            Typ der Objekte in der Liste.
     * @param list
     *            Liste mit den Objekten.
     * @param applier
     *            Methode die ein Objekt in einen String wandelt.
     * @return Liste mit den umgewandelten Ergebnissen.
     */
    public static <T> List<String> applyToList(List<T> list, ListApplier<T> applier) {
        List<String> results = new ArrayList<>();

        for (T value : list) {
            String result =  applier.apply(value);
            results.add(result);
        }

        return results;
    }

    /** Sortiert die übergebene Liste von Integers aufsteigend. */
    public static void sortAscanding(List<Integer> list) {
        Collections.sort(list, new Comparator<Integer>() {
            @Override
            public int compare(Integer int1, Integer int2) {
                return int1 - int2;
            }
        });
    }

    /** Sortiert die übergebene Liste von Integers absteigend. */
    public static void sortDescanding(List<Integer> list) {
        Collections.sort(list, new Comparator<Integer>() {
            @Override
            public int compare(Integer int1, Integer int2) {
                return int2 - int1;
            }
        });
    }

    /**
     * Entfernt die angegebenen Indices aus der Liste.
     *
     * Die Liste der zu löschenden Indices kann in beliebiger Reihenfolge übergeben werden und darf
     * Elemente mehrfach enthalten, gelöscht wird der betreffende Index natürlich nur einmal.
     *
     * @param list
     *            Zu verändernde Liste.
     * @param indicesToDelete
     *            Liste mit zu löschenden Indices.
     */
    public static void removeElementsFromListByIndices(List<?> list,
            List<Integer> indicesToDelete) {
        List<Integer> ownIndicesToDelete = CollectionsHelper.createDisjunctList(indicesToDelete);
        CollectionsHelper.sortDescanding(ownIndicesToDelete);
        for (int index : ownIndicesToDelete) {
            list.remove(index);
        }
    }

    /**
     * Erstellt die Beschreibung des Unterschieds zweier Listen.
     *
     * @param <T>
     *            Typ der Listenelemente.
     * @param list1
     *            Erste Liste.
     * @param description1
     *            Beschreibung der ersten Liste.
     * @param list2
     *            Zweite Liste.
     * @param description2
     *            Beschreibung der zweiten Liste.
     * @return
     */
    public static <T> String createListDifference(List<T> list1, String description1,
            List<T> list2, String description2) {
        StringBuilder builder = new StringBuilder()
                .append("Vergleich der Listen '" + description1 + "' (1) und '"
                        + description2 + "' (2):")
                .append(Text.LINE_BREAK)
                ;
        int size1 = list1.size();
        int size2 = list2.size();
        if (size1 == size2) {
            builder
                    .append("Beide Listen haben " + NumberString.taupu(size1) + " Elemente.")
                    .append(Text.LINE_BREAK)
                    ;
        }
        else {
            builder
                    .append("Die Listen haben eine unterschiedliche Anzahl an Elementen!")
                    .append(Text.LINE_BREAK)
                    .append("    Liste 1 hat " + NumberString.taupu(size1) + " Elemente.")
                    .append(Text.LINE_BREAK)
                    .append("    Liste 2 hat " + NumberString.taupu(size2) + " Elemente.")
                    .append(Text.LINE_BREAK)
                    ;
        }

        int countDifferentElementsOnCommonIndices = 0;
        int minLength = Math.min(size1, size2);
        for (int index = 0; index < minLength; ++index) {
            T element1 = list1.get(index);
            T element2 = list2.get(index);
            if (!element1.equals(element2)) {
                ++countDifferentElementsOnCommonIndices;
                builder
                        .append("Element Nummer " + (index + 1) + " (also am Index " + index
                                + ") ist verschieden:")
                        .append(Text.LINE_BREAK)
                        .append("    Liste 1 hat dort das Element '" + element1 + "'.")
                        .append(Text.LINE_BREAK)
                        .append("    Liste 2 hat dort das Element '" + element2 + "'.")
                        .append(Text.LINE_BREAK)
                        ;
            }
        }
        if (size1 > size2) {
            builder
                    .append("Weitere Elemente in Liste 1:")
                    .append(Text.LINE_BREAK)
                    ;
            for (int index = minLength; index < size1; ++index) {
                T element = list1.get(index);
                builder
                        .append("    " + (index + 1) + " : " + element)
                        .append(Text.LINE_BREAK)
                        ;
            }
        }
        else if (size2 > size1) {
            builder
                    .append("Weitere Elemente in Liste 2:")
                    .append(Text.LINE_BREAK)
                    ;
            for (int index = minLength; index < size2; ++index) {
                T element = list2.get(index);
                builder
                        .append("    " + (index + 1) + " : " + element)
                        .append(Text.LINE_BREAK)
                        ;
            }
        }
        if (0 == countDifferentElementsOnCommonIndices) {
            builder
                    .append("Die Elemente an gemeinsamen Indices sind in beiden Listen identisch.")
                    .append(Text.LINE_BREAK)
                    ;
        }
        else {
            builder
            .append("Die beiden Listen haben "
                    + NumberString.taupu(countDifferentElementsOnCommonIndices)
                    + " unterschiedliche(s) Element(e) an an gemeinsamen Indices.")
            .append(Text.LINE_BREAK)
            ;
        }

        return builder.toString();
    }

    /**
     * Überprüft, ob die Liste das Element enthält, wobei die Groß-Kleinschreibung egal ist.
     *
     * @param list
     *            Zu überprüfende Liste.
     * @param element
     *            Zu überprüfendes Element.
     */
    public static boolean containsIgnoreCase(List<String> list, String element) {
        for (String listElement : list) {
            if (element.equalsIgnoreCase(listElement)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Fügt in eine Liste weitere Elemente an bestimmter Stelle ein.
     *
     * @param originalList
     *            Originale Liste, in deren Kopie zusätzliche Daten eingefügt werden sollen
     * @param newElements
     *            Die einzufügenden Daten.
     * @param insertColumn
     *            Spalten-Index, ab dem Einzufügen ist. Kann auch originalList.size() sein!
     */
    public static List<String> mixIntoList(List<String> originalList, List<String> newElements,
            int insertColumn) {
        List<String> outputList = new ArrayList<>();

        int newElementsSize = newElements.size();
        int outputSize = originalList.size() + newElementsSize;

        for (int outputIndex = 0; outputIndex < outputSize; ++outputIndex) {
            if (outputIndex < insertColumn) {
                String element = originalList.get(outputIndex);
                outputList.add(element);
            }
            else if (outputIndex >= insertColumn
                    && outputIndex < insertColumn + newElementsSize) {
                String element = newElements.get(outputIndex - insertColumn);
                outputList.add(element);
            }
            else {
                String element = originalList.get(outputIndex - newElementsSize);
                outputList.add(element);
            }
        }

        return outputList;
    }

    /** Erstellt eine Liste bestehend aus so vielen leeren Strings wie angegeben. */
    public static List<String> createEmptyStringList(int size) {
        List<String> list = new ArrayList<>();
        for (int index = 0; index < size;  ++index) {
            list.add("");
        }
        return list;
    }

    /**
     * Ermittelt zu einem Verzeichnis und einem gegebenen Wert den ersten Index der Map, an dem
     * dieser Wert hinterlegt ist. Falls der Wert in dem Verzeichnis nicht enthalten ist, wird der
     * notFoundReturnKey zurückgegeben.
     *
     * Achtung: Diese Methode ist langsam. Zu Analyezwecken durchaus brauchbar, sollte man im
     * Wirkbetrieb lieber ein zusätzliches Verzeichnis in Gegenrichtung erstellen, denn sonst
     * hebelt man den den Geschwindigkeitsvorteil einer Map wieder aus, da doch über die Schlüssel
     * iteriert wird, statt in O(1) direkt auf den hinterlegten Wert zugreifen zu können.
     *
     * @param <K>
     *            Klasse der Schlüssel des Verzeichnis, muss mit compareTo() vergleichbar sein,
     *            also Comparable<K> erweitern.
     * @param <V>
     *            Klasse der Werte des Verzeichnisses
     * @param map
     *            Das Verzeichnis.
     * @param searchValue
     *            Der Wert dessen Schlüssel man sucht.
     * @param notFoundReturnKey
     *            Der zurückgegebene Schlüssel, falls der Wert nicht gefunden wurde.
     * @return Der gefundene Schlüssel oder aber notFoundReturnKey, falls der Wert im Verzeichnis
     *         nicht gefunden wird.
     */
    public static <K extends Comparable<K>, V> K getFirstMapKeyByValue(Map<K, V> map,
            V searchValue, K notFoundReturnKey) {
        K firstKey = notFoundReturnKey;
        boolean found = false;

        for (K key : map.keySet()) {
            V value = map.get(key);
            if (value.equals(searchValue)) {
                if (found) {
                    if (key.compareTo(firstKey) < 0) {
                        firstKey = key;
                    }
                }
                else {
                    firstKey = key;
                    found = true;
                }
            }
        }

        return firstKey;
    }

    /**
     * Gibt den Index des letzten Elements der Liste zurück. Ist diese leer, wird -1 zurück
     * gegeben.
     *
     * @param list
     *            Liste deren letzter Index zurückgegeben werden soll.
     */
    public static <T> int getLastListIndex(List<T> list) {
        if (list.isEmpty()) {
            return -1;
        }
        else {
            return list.size() - 1;
        }
    }

    /**
     * Gibt das letzte Element der Liste zurück. Ist diese leer, wird eine Ausnahme geworfen.
     *
     * @param list
     *            Liste deren letztes Element zurückgegeben werden soll.
     */
    public static <T> T getLastListMember(List<T> list) {
        if (list.isEmpty()) {
            throw new IllegalArgumentException("Die übergebene Liste ist leer.");
        }
        else {
            return list.get(getLastListIndex(list));
        }
    }

    /**
     * Gibt das letzte Element der Liste zurück. Ist diese leer, wird returnValueInCaseOfEmptyList
     * zurück gegeben.
     *
     * @param list
     *            Liste deren letztes Element zurückgegeben werden soll.
     * @param returnValueInCaseOfEmptyList
     *            Wird zurückgegeben, wenn die Liste leer ist.
     */
    public static <T> T getLastListMember(List<T> list, T returnValueInCaseOfEmptyList) {
        if (list.isEmpty()) {
            return returnValueInCaseOfEmptyList;
        }
        else {
            return getLastListMember(list);
        }
    }

    /** Prüft, ob das übergebene Element mindestens zweimal in der Liste enthalten ist. */
    public static <Element> boolean containedMoreThanOnce(List<Element> list, Element element) {
        return containedMinNTines(list, element, 2);
    }

    /**
     * Prüft, ob das übergebene Element mindestens n-mal in der Liste enthalten ist.
     *
     * @param <Element>
     *            Klasse des Listenelements.
     * @param list
     *            Liste mit Elementen.
     * @param element
     *            Zu überprüfendes Element.
     * @param times
     *            Anzahl, wie oft das Element enthalten sein soll.
     */
    public static <Element> boolean containedMinNTines(List<Element> list, Element element,
            int times) {
        int count = 0;

        for (Element e : list) {
            if (e.equals(element)) {
                ++count;
                if (count >= times) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Zählt wie oft das übergebene Element in der Liste enthalten ist.
     *
     * @param <Element>
     *            Klasse des Listenelements.
     * @param list
     *            Liste mit Elementen.
     * @param element
     *            Zu überprüfendes Element.
     */
    public static <Element> int countElementInList(List<Element> list, Element element) {
        int count = 0;

        for (Element e : list) {
            if (e.equals(element)) {
                ++count;
            }
        }

        return count;
    }

    /**
     * Gibt die übergebene Liste so aus, wie man sie in einem Unittest testen würde, mit
     * Überprüfung der Länge und aller Elemente.
     *
     * @param list
     *            Zu überprüfende Liste.
     */
    public static void printAssertEquals(List<String> list) {
        printAssertEquals(list, "list");
    }

    /**
     * Gibt die übergebene Liste so aus, wie man sie in einem Unittest testen würde, mit
     * Überprüfung der Länge und aller Elemente.
     *
     * @param list
     *            Zu überprüfende Liste.
     * @param listVariable
     *            Name der Listenvariablen.
     */
    public static void printAssertEquals(List<String> list, String listVariable) {
        System.out.println(
                "        assertEquals(" + list.size() + ", " + listVariable + ".size());");
        for (int index = 0; index < list.size(); ++index) {
            System.out.println("        assertEquals(\""
                    + list.get(index).replace("\"", "\\\"")
                    + "\", " + listVariable + ".get("
                    + Text.fillWithSpacesAtFront(Integer.toString(index), 2)
                    + "));");
        }
    }

    /**
     * Erzeugt eine Aufzählung der Form '1', '2' oder '3' aus einer Liste.
     *
     * @param list
     *            Die Liste deren Werte ausgegeben werden sollen.
     * @param conjunction
     *            Verbindungswort der letzten zwei Elemente, z. B. "und".
     */
    public static String toEnumeration(List<String> list, String conjunction) {
        StringBuilder builder = new StringBuilder();

        if (list.isEmpty()) {
            // nichts zu tun
        }
        else if (list.size() == 1) {
            builder.append("'").append(list.get(0)).append("'");
        }
        else {
            for (int index = 0; index < list.size() - 2; ++ index) {
                builder.append("'").append(list.get(index)).append("', ");
            }
            builder.append("'").append(list.get(list.size() - 2)).append("' ");
            builder.append(conjunction);
            builder.append(" '").append(list.get(list.size() - 1)).append("'");
        }

        return builder.toString();
    }

    /**
     * Gibt eine Sublist als eigenständige Liste zurück.
     *
     * Anders als list.sublist(...), was gern zu einer ConcurrentModificationException führt.
     *
     * @param <Element>
     *            Klasse des Listenelements.
     * @param list
     *            Liste aus der eine Sublist erzeugt werden soll.
     * @param startIndex
     *            Ab diesem Index (einschließlich) werden alle Elemente in die Sublist übernommen.
     */
    public static <Element> List<Element> sublist(List<Element> list, int startIndex) {
        return sublist(list, startIndex, list.size());
    }

    /**
     * Gibt eine Sublist als eigenständige Liste zurück.
     *
     * Anders als list.sublist(...), was gern zu einer ConcurrentModificationException führt.
     *
     * @param <Element>
     *            Klasse des Listenelements.
     * @param list
     *            Liste aus der eine Sublist erzeugt werden soll.
     * @param startIndex
     *            Ab diesem Index (einschließlich) werden die Elemente in die Sublist übernommen.
     * @param endIndex
     *            Bis zu diesem Index (ausschließlich) werden die Elemente in die Sublist
     *            übernommen.
     */
    public static <Element> List<Element> sublist(List<Element> list, int startIndex, int endIndex) {
        List<Element> sublist = new ArrayList<>();

        for (int index = startIndex; index < endIndex; ++index) {
            sublist.add(list.get(index));
        }

        return sublist;
    }

    /**
     * Gibt eine Sublist mit allen Zeilen außer der ersten und der letzten als eigenständige Liste zurück.
     *
     * Anders als list.sublist(...), was gern zu einer ConcurrentModificationException führt.
     *
     * @param <Element>
     *            Klasse des Listenelements.
     * @param list
     *            Liste aus der eine Sublist erzeugt werden soll.
     */
    public static <Element> List<Element> sublistWithoutFirstAndLastLine(List<Element> list) {
        if (list.isEmpty()) {
            throw new RuntimeException("Es wurde eine leere Liste übergeben.");
        }
        return sublist(list, 1, list.size() - 1);
    }

   /**
     * Gibt den Index des ersten Elements der Liste zurück, das den gesuchten Begriff enthält.
     *
     * @param list
     *            Zu durchsuchende Liste.
     * @param search
     *            Suchbegriff.
     * @return Index des Elements oder -1, wenn keines gefunden wird.
     */
    public static int determineIndexOfFirstElementContaining(List<String> list, String search) {
        return determineIndexOfFirstElementContaining(list, search, 0);
    }

    /**
     * Gibt den Index des ersten Elements der Liste zurück, das den gesuchten Begriff enthält.
     *
     * @param list
     *            Zu durchsuchende Liste.
     * @param search
     *            Suchbegriff.
     * @param startIndex
     *            Der Index der ersten Zeile, ab der gesucht wird.
     * @return Index des Elements oder -1, wenn keines gefunden wird.
     */
    public static int determineIndexOfFirstElementContaining(List<String> list, String search,
            int startIndex) {
        for (int index = startIndex; index < list.size(); ++index) {
            String element = list.get(index);
            if (element.contains(search)) {
                return index;
            }
        }

        return -1;
    }

    /** Gibt eine Liste zurück mit den Elementen der Liste, ergänzt um ein Leerzeichen. */
    public static List<String> addSpaceToAllListMembers(List<String> list) {
        return addToAllListMembers(list, " ");
    }

    /**
     * Gibt eine Liste zurück mit den Elementen der Liste, ergänzt um den hinzuzufügenden Ausdruck.
     *
     * @param list
     *            Liste, an deren Elemente etwas angehängt werden soll.
     * @param additional
     *            Anhängsel, das hinten am jedes Listenelement angefügt werden soll.
     * @return Liste mit den verlängerten Elementen.
     */
    public static List<String> addToAllListMembers(List<String> list, String additional) {
        List<String> results = new ArrayList<>();

        for (String element : list) {
            String result = element + additional;
            results.add(result);
        }

        return results;
    }

    /**
     * Gibt eine Liste zurück mit den Elementen der Liste, vorn ergänzt um den hinzuzufügenden
     * Ausdruck.
     *
     * @param list
     *            Die Liste, vor deren Elemente etwas eingefügt werden soll.
     * @param intro
     *            Der Vorsatz, das vorn an jedes Listenelement angefügt werden soll.
     * @return Liste mit den verlängerten Elementen.
     */
    public static List<String> addBeforeAllListMembers(List<String> list, String intro) {
        List<String> results = new ArrayList<>();

        for (String element : list) {
            String result = intro + element;
            results.add(result);
        }

        return results;
    }

    /**
     * Gibt eine Liste zurück mit den Elementen der Liste, vorn ergänzt um den hinzuzufügenden
     * Ausdruck.
     *
     * @param list
     *            Die Liste, vor deren Elemente etwas eingefügt werden soll.
     * @param intros
     *            Die Vorsätze, welche vorn an jedes Listenelement angefügt werden soll.
     * @return Liste mit den verlängerten Elementen.
     */
    public static List<String> addAllBeforeAllListMembers(List<String> list, List<String> intros) {
        List<String> results = new ArrayList<>();

        for (String intro : intros) {
            results.addAll(addBeforeAllListMembers(list, intro));
        }

        return results;
    }

    /**
     * Prüft ob der übergebene Text in mindestens einem Element der Liste enthalten ist.
     *
     * @param text
     *            Der Text der innerhalb eines Listenelements gesucht wird.
     * @param list
     *            Die Liste deren Werte nach dem Text durchsucht werden.
     * @return Wahrheitswert.
     */
    public static boolean containedInAnyListElement(String text, List<String> list) {
        for (String element : list) {
            if (element.contains(text)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Prüft ob der übergebene Text in mindestens einem Element der Liste enthalten ist, wobei die
     * Groß/Kleinschreibung ignoriert wird.
     *
     * @param text
     *            Der Text der innerhalb eines Listenelements gesucht wird.
     * @param list
     *            Die Liste deren Werte nach dem Text durchsucht werden.
     * @return Wahrheitswert.
     */
    public static boolean containedInAnyListElementIgnoreCase(String text, List<String> list) {
        String loweredText = Text.toLowerCase(text);
        List<String> loweredList = toLowerCase(list);
        return containedInAnyListElement(loweredText, loweredList);
    }

    /** Sortiert eine Liste von Integern in Stringform nach Integern. */
    public static void sortNumberStringsAsInteger(List<String> numberStrings) {
        Collections.sort(numberStrings, new Comparator<String>() {
            @Override
            public int compare(String number1, String number2) {
                int int1 = NumberString.parseInt(number1);
                int int2 = NumberString.parseInt(number2);
                return int1 - int2;
            }
        });
    }

    /** Fügt in die Liste Leerzeilen zwischen je zwei Zeilen ein. */
    public static void addEmptyLines(List<String> list) {
        for (int index = list.size() - 1; index > 0; --index) {
            list.add(index, "");
        }
    }

    /**
     * Erstellt aus einem Verzeichnis (Liste von äquivalenten Listen) eine flache Liste mit allen
     * Ausprägungen.
     *
     * Wird nach der Umstellung auf Dictionary eigentlich nicht mehr benötigt!
     */
    @Deprecated
    public static List<String> createFlatListFromDictionary(List<List<String>> dictionary) {
        List<String> flatList = new ArrayList<>();

        for (List<String> equalThingsList : dictionary) {
            flatList.addAll(equalThingsList);
        }

        return flatList;
    }

    /** Entfernt bei allen Elementen der Liste den Doppelpunkt am Ende, wenn vorhanden. */
    public static void removeColonAtEnd(List<String> list) {
        for (int index = 0; index < list.size(); ++index) {
            String element = list.get(index);
            if (element.endsWith(":")) {
                element = element.substring(0, element.length() - 1);
                element = element.trim();
                list.set(index, element);
            }
        }
    }

    /**
     * Erzeugt aus der Liste eine mit allen Einträgen verdoppelt, einmal mit einem Doppelpunkt
     * dahinter, einmal so wie sie sind.
     */
    public static List<String> withAndWithoutColon(List<String> inputList) {
        List<String> outputList = new ArrayList<>();
        for (String input : inputList) {
            outputList.addAll(withAndWithoutColon(input));
        }
        return outputList;
    }

    private static List<String> withAndWithoutColon(String input) {
        List<String> list = new ArrayList<>();
        list.add(input + ":");
        list.add(input);
        return list;
    }

    /**
     * Erzeugt aus der Liste eine mit allen Einträgen verdoppelt, einmal mit einem Semikolon
     * dahinter, einmal mit einem Komma dahinter, einmal so wie sie sind.
     */
    public static List<String> withAndWithoutComma(List<String> inputList) {
        List<String> outputList = new ArrayList<>();
        for (String input : inputList) {
            outputList.addAll(withAndWithoutComma(input));
        }
        return outputList;
    }

    private static List<String> withAndWithoutComma(String input) {
        List<String> list = new ArrayList<>();
        list.add("; " + input);
        list.add(", " + input);
        list.add(input);
        return list;
    }

    /**
     * Wandelt eine Liste von Zeilen zu einer Liste mit Listen von Feldern (einer Zeile) um.
     *
     * @param lines
     *            Die Liste mit Zeilen.
     * @return Liste mit den Listen von Strings, je eine Liste pro Zeile.
     */
    public static List<List<String>> listOfLinesToListOfFieldLines(List<String> lines) {
        List<List<String>> fieldLines = new ArrayList<>();

        for (String line : lines) {
            List<String> fields = Text.splitByTabulator(line);
            fieldLines.add(fields);
        }

        return fieldLines;
    }

    /**
     * Wandelt eine Liste mit Listen von Feldern (einer Zeile) zu einer Liste von Zeilen um.
     *
     * @param fieldsList
     *            Die Liste mit Listen von Feldern (einer Zeile).
     * @return Liste mit den Strings, je einer pro Zeile.
     */
    public static List<String> listOfFieldLinesToListOfLines(List<List<String>> fieldsList) {
        List<String> lines = new ArrayList<>();

        for (List<String> fields : fieldsList) {
            String line = Text.joinWithTabulator(fields);
            lines.add(line);
        }

        return lines;
    }

    /**
     * Erzeugt eine neue Liste, in der Werte, die aus einem oder mehreren Leerzeichen bestehen, zum
     * leeren String werden.
     */
    public static List<String> changeOnlySpacesToEmptyString(List<String> list) {
        List<String> cleanedList = new ArrayList<>();

        for (String element : list) {
            String cleanedElement;
            if (element.isBlank()) {
                cleanedElement = "";
            }
            else {
                cleanedElement = element;
            }

            cleanedList.add(cleanedElement);
        }

        return cleanedList;
    }

    /**
     * Ersetzt in allen Elementen der Liste Pipe-Zeichen durch Schrägstriche.
     *
     * Die Liste wird geändert.
     */
    public static void switchPipesToSlashes(List<String> list) {
        for (int index = 0; index < list.size(); ++index) {
            String element = list.get(index);
            element = element.replace("|", "/");
            list.set(index, element);
        }
    }

    /**
     * Ersetzt in einer Liste mit Zeilen alle Stellen, an denen die gesuchten Zeile vorkommen diese
     * durch die neuen Zeilen.
     *
     * @param lines
     *            Liste mit allen Zeilen.
     * @param linesToExchange
     *            Liste mit den zu ersetzenden Zeilen.
     * @param newLines
     *            Liste mit den einzufügenden Zeilen.
     */
    public static void replaceMultipleLines(List<String> lines, List<String> linesToExchange,
            List<String> newLines) {
        int actualLinesIndex = 0;
        int numberOfMatchingLines = 0;
        while (actualLinesIndex < lines.size()) {
            String actualLine = lines.get(actualLinesIndex);
            String actualSearchLine = linesToExchange.get(numberOfMatchingLines);
            ++actualLinesIndex;
            if (actualLine.equals(actualSearchLine)) {
                ++numberOfMatchingLines;
                if (numberOfMatchingLines == linesToExchange.size()) {
                    int startIndexInLines = actualLinesIndex - numberOfMatchingLines;
                    for (int del = 0; del < linesToExchange.size(); ++del) {
                        lines.remove(startIndexInLines);
                    }
                    for (int newIndex = newLines.size() - 1; newIndex >= 0; --newIndex) {
                        String newLine = newLines.get(newIndex);
                        lines.add(startIndexInLines, newLine);
                    }
                    actualLinesIndex = startIndexInLines + newLines.size();
                    numberOfMatchingLines = 0;
                }
            }
        }
    }

    /**
     * Entfernt am Ende eines jeden Elements ein Komma, falls vorhanden.
     *
     * @param list
     *            Die zu bearbeitende Liste.
     */
    public static void removeCommaAtEnd(List<String> list) {
        for (int index = 0; index < list.size(); ++index) {
            String element = list.get(index);
            element = Text.removeTextAtEndIfEndsWith(element, ",");
            list.set(index, element);
        }
    }

    /**
     * Ermittelt den ersten Index, dessen Listenelement mit dem übergebenen Anfang beginnt.
     *
     * @param list
     *            Die zu durchsuchende Liste.
     * @param searchStart
     *            Der zu suchende Anfang eines Listenelements.
     * @return Erster Index der die Bedingungen erfüllt oder -1, wenn kein solcher gefunden wurde.
     */
    public static int determineFirstIndexStartingWith(List<String> list, String searchStart) {
        return determineFirstIndexStartingWith(list, searchStart, 0);
    }

    /**
     * Ermittelt den ersten Index größer-gleich dem übergebenen Suchindex, dessen Listenelement mit
     * dem übergebenen Anfang beginnt.
     *
     * @param list
     *            Die zu durchsuchende Liste.
     * @param searchStart
     *            Der zu suchende Anfang eines Listenelements.
     * @param startIndex
     *            Der Index ab dem die Elemente durchsucht werden.
     * @return Erster Index der die Bedingungen erfüllt oder -1, wenn kein solcher gefunden wurde.
     */
    public static int determineFirstIndexStartingWith(List<String> list, String searchStart,
            int startIndex) {
        for (int index = startIndex; index < list.size(); ++index) {
            String element = list.get(index);
            if (element.startsWith(searchStart)) {
                return index;
            }
        }

        return -1;
    }

    /**
     * Ermittelt alle Indices, deren Listenelement mit dem übergebenen Anfang beginnt.
     *
     * @param list
     *            Die zu durchsuchende Liste.
     * @param searchStart
     *            Der zu suchende Anfang eines Listenelements.
     * @return Alle Indices die die Bedingungen erfüllt oder die leere Menge, wenn kein solcher
     *         gefunden wurde.
     */
    public static List<Integer> determineAllIndicesStartingWith(List<String> list,
            String searchStart) {
        List<Integer> indices = new ArrayList<>();

        for (int index = 0; index < list.size(); ++index) {
            String element = list.get(index);
            if (element.startsWith(searchStart)) {
                indices.add(index);
            }
        }

        return indices;
    }

    /**
     * Erstellt die Schlüssel (Strings) eines Verzeichnisses welches Stings auf Integer abbildet
     * absteigend sortiert nach der Häufigkeit (Integer).
     *
     * Bei den Anzahlen mit gleicher Häufigkeit werden die Schlüssel aufsteigend alphabetisch
     * sortiert.
     *
     * @param map
     *            Das Verzeichnis, dessen Schlüssel sortiert zurückgegeben werden sollen.
     */
    public static List<String> createSortedKeysForStringIntegerMapByNumberDescanding(
            Map<String, Integer> map) {
        List<String> keys = new ArrayList<>(map.keySet());
        Collections.sort(keys, new Comparator<String>() {
            @Override
            public int compare(String key1, String key2) {
                int number1 = map.get(key1);
                int number2 = map.get(key2);
                if (number1 != number2) {
                    return number2 - number1;
                }

                return key1.compareTo(key2);
            }
        });
        return keys;
    }

    private static final List<String> COMMA_POINT_ETC = buildListFrom(",", ".", ":", ";");

    /**
     * Vervielfacht die übergebene Liste, indem jedes Element so wie es ist und je einmal mit
     * angehängtem Komma, Punkt und Doppelpunkt erzeugt wird.
     */
    public static List<String> appendCommaPointEtc(List<String> inputlist) {
        List<String> outputlist = new ArrayList<>();

        for (String input : inputlist) {
            for (String additional : COMMA_POINT_ETC) {
                outputlist.add(input + additional);
            }
            outputlist.add(input);
        }

        return outputlist;
    }

    /**
     * Vertauscht die beiden Elemente der übergebenen Liste.
     *
     * @param <T>
     *            Typ der Elemente der Liste.
     * @param list
     *            Die Liste, die modifiziert werden soll.
     * @param index1
     *            Der Index des ersten zu vertauschenden Elemente.
     * @param index2
     *            Der Index des zweiten zu vertauschenden Elemente.
     */
    public static <T> void switchListElements(List<T> list, int index1, int index2) {
        T element1 = list.get(index1);
        T element2 = list.get(index2);

        list.set(index1, element2);
        list.set(index2, element1);
    }

    /**
     * Listet ein Verzeichnis schön auf.
     *
     * @param map
     *            Das Verzeichnis.
     * @param orderedKeys
     *            Die Schlüssel in der gewünschten Reihenfolge.
     */
    public static String listMapNice(Map<String, List<Integer>> map,
            List<String> orderedKeys) {
        StringBuilder builder = new StringBuilder();

        int number = 0;
        for (String key : orderedKeys) {
            if (!map.containsKey(key)) {
                throw new RuntimeException("Das Verzeichnis enthält nicht den gewünschten "
                        + "Schlüssel '" + key + "'.\n");
            }
            ++number;
            String element = String.format("    %2d. %s -> %s", number, key,
                    map.get(key).toString());
            builder.append(element).append(Text.LINE_BREAK);
        }

        return builder.toString();
    }

    /**
     * Gibt eine Liste zurück, die statt der ursprünglichen Dateinamen mit Pfad nun solche ohne
     * Pfad enthält. Dafür wird für jeden Dateinamen der Teil des übergebenen Dateinamens nach dem
     * letzten Slash oder Backslash ermittelt.
     *
     * @param filenames
     *            Die Liste mit den Dateinamen mit Pfad.
     * @return Die erzeugte Liste mit den Dateinamen ohne Pfad.
     */
    public static List<String> toBareFilenames(List<String> filenames) {
        List<String> bareFilenames = new ArrayList<>();

        for (String filename : filenames) {
            String bareFilename = FileHelper.getBareName(filename);
            bareFilenames.add(bareFilename);
        }

        return bareFilenames;
    }

    /**
     * Erzeugt das kartesische Produkt zweier Listen.
     *
     * Das kartesische Produkt zweier Mengen ist die Menge aller geordneten Paare von Elementen der
     * beiden Mengen, wobei die erste Komponente ein Element der ersten Menge und die zweite
     * Komponente ein Element der zweiten Menge ist.
     *
     * @param list1
     *            Die erste Liste.
     * @param between
     *            Einschub der zwischen die Elemente aus der ersten und der zweiten Liste kommt.
     * @param list2
     *            Die zweite Liste.
     * @return Das kartesische Produkt der beiden Listen.
     */
    public static List<String> createCartesianProduct(List<String> list1, String between,
            List<String> list2) {
        List<String> resultList = new ArrayList<>();

        for (String first : list1) {
            for (String second : list2) {
                String element = first + between + second;
                resultList.add(element);
            }
        }

        return resultList;
    }

    /**
     * Prüft ob die zwei Listen die gleiche Anzahl an Elementen haben. Wenn nicht, wird eine
     * Ausnahme geworfen.
     */
    public static <T> void checkSizes(List<T> list1, List<T> list2) {
        int size1 = list1.size();
        int size2 = list2.size();
        if (size1 != size2) {
            throw new RuntimeException("Die beiden übergebenen Listen sind nicht gleich groß.\n"
                    + "Elemente aus der ersten Liste:\n"
                    + listListIndentedWithoutNumberAndBracket(list1)
                    + "Elemente aus der zweiten Liste:\n"
                    + listListIndentedWithoutNumberAndBracket(list2)
                    );
        }
    }

    /**
     * Überprüft ob die zweite Liste alle Elemente der ersten Liste enthält.
     *
     * Ist dies nicht der Fall, wird eine Ausnahme geworfen.
     */
    public static <T> void checkAllElementsOfFirstAreInSecond(List<T> list1, List<T> list2) {
        for (T element : list1) {
            if (!list2.contains(element)) {
                throw new RuntimeException("Ein Element aus der ersten Liste ist nicht in der"
                        + "zweiten Liste enthalten.\n"
                        + "\t" + "nicht enthaltendes Element:\n"
                        + element + "\n"
                        + "Elemente aus der ersten Liste:\n"
                        + listListIndentedWithoutNumberAndBracket(list1)
                        + "Elemente aus der zweiten Liste:\n"
                        + listListIndentedWithoutNumberAndBracket(list2)
                        );
            }
        }
    }

    /** Prüft ob die zweite Liste alle Elemente der ersten Liste enthält. */
    public static <T> boolean areAllElementsOfFirstAreInSecond(List<T> list1, List<T> list2) {
        for (T element : list1) {
            if (!list2.contains(element)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Erhöht in einem Verzeichnis von Strings mit der Anzahl ihres Vorkommens die Anzahl um 1.
     *
     * Kam der String bislang nicht vor, wird er mit der Anzahl 1 hinzugefügt.
     *
     * @param mapWithQuantity
     *            Das Verzeichnis von Strings mit der Anzahl ihres Vorkommens.
     * @param key
     *            Der Wert, dessen Anzahl erhöht werden soll.
     */
    public static void addToMapWithQuantities(Map<String, Integer> mapWithQuantity, String key) {
        addToMapWithQuantities(mapWithQuantity, key, 1);
    }

    /**
     * Erhöht in einem Verzeichnis von Strings mit der Anzahl ihres Vorkommens die Anzahl um die
     * übergebene Anzahl.
     *
     * Kam der String bislang nicht vor, wird er mit der übergebenen Anzahl hinzugefügt.
     *
     * @param mapWithQuantity
     *            Das Verzeichnis von Strings mit der Anzahl ihres Vorkommens.
     * @param key
     *            Der Wert, dessen Anzahl erhöht werden soll.
     * @param additionalQuantity
     *            Die Anzahl um die die gespeicherte Anzahl erhöhrt werden soll.
     */
    public static void addToMapWithQuantities(Map<String, Integer> mapWithQuantity, String key,
            int additionalQuantity) {
        if (!mapWithQuantity.containsKey(key)) {
            mapWithQuantity.put(key, 0);
        }
        int quantity = mapWithQuantity.get(key);
        quantity += additionalQuantity;
        mapWithQuantity.put(key, quantity);
    }

    /**
     * Entfernt aus einer Liste mit Strings alle Elemente, die mit dem übergebenen Anfang beginnen.
     *
     * @param list
     *            Die Liste mit den Strings.
     * @param entryStart
     *            Der Beginn solcher Zeilen, welche entfernt werden sollen.
     */
    public static void removeEntriesStartingWith(List<String> list, String entryStart) {
        List<Integer> indicesToRemove = new ArrayList<>();
        for (int index = 0; index < list.size(); ++index) {
            String entry = list.get(index);
            if (entry.startsWith(entryStart)) {
                indicesToRemove.add(index);
            }
        }

        Collections.reverse(indicesToRemove);

        for (int indexToRemove : indicesToRemove) {
            list.remove(indexToRemove);
        }
    }

    /** Erzeugt aus der Liste der Integerwerte eine Liste mit 'schönen' Strings. */
    public static List<String> createStringListFromIntegerList(List<Integer> numberList) {
        List<String> outputList = new ArrayList<>();

        for (int number : numberList) {
            String output = NumberString.taupu(number);
            outputList.add(output);
        }

        return outputList;
    }

    /**
     * Entfernt das erste und letzte Element der Liste. Hat die Liste weniger als drei Elemente,
     * wird die geleert.
     */
    public static <T> void removeFirstAndLastElement(List<T> list) {
        if (list.size() < 3) {
            list.clear();
        }
        else {
            list.remove(0);
            int lastIndex = list.size() - 1;
            list.remove(lastIndex);
        }
    }

    /**
     * Entfernt das erste Element der Liste. Hat die Liste weniger als zwei Elemente, wird sie
     * geleert.
     */
    public static <T> void removeFirstElement(List<T> list) {
        if (list.size() < 2) {
            list.clear();
        }
        else {
            list.remove(0);
        }
    }

    /**
     * Entfernt das letzte Element der Liste. Hat die Liste weniger als zwei Elemente, wird sie
     * geleert.
     */
    public static <T> void removeLastElement(List<T> list) {
        if (list.size() < 2) {
            list.clear();
        }
        else {
            int lastIndex = list.size() - 1;
            list.remove(lastIndex);
        }
    }

    /** Zählt wieviele Zeilen mit dem übergebenen Anfang beginnen. */
    public static int countLinesStartingWith(List<String> lines, String intro) {
        int count = 0;

        for (String line : lines) {
            if (line.startsWith(intro)) {
                ++count;
            }
        }

        return count;
    }

    /**
     * Gibt an, ob alle Element blank sind (also ob sie leer sind oder nur aus Whitespace
     * bestehen).
     */
    public static boolean areAllElementsBlank(List<String> list) {
        for (String element : list) {
            if (!element.isBlank()) {
                return false;
            }
        }

        return true;
    }

    /**
     * Sortiert eine Liste mit Strings auf natürlichere Weise als es via Collections.sort(list)
     * gemacht wird, was text1.compareTo(text2) verwendet, denn da kommt dann 'Z' vor 'a' und
     * Umlaute werden richtig behandelt.
     */
    public static void sortBetterAlphabethical(List<String> list) {
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String e1, String e2) {
                return Text.compareStringsBetter(e1, e2);
            }
        });
    }

    /**
     * Prüft ob die übergebene Liste aufsteigend sortiert ist.
     *
     * Gleiche Elemente sind nicht erlaubt.
     */
    public static boolean isSortedIntegerList(List<Integer> numbers) {
        if (numbers.size() < 2) {
            return true;
        }

        int lastNumber = Integer.MIN_VALUE;
        for (int number : numbers) {
            if (number <= lastNumber) {
                return false;
            }
            lastNumber = number;
        }

        return true;
    }

    /**
     * Entfernt aus einer Liste mit Listen von Integers alle diejenigen, bei denen alle Zahlen in
     * einer anderen Liste enthalten sind.
     *
     * Hier werden die Daten in listOfLists also von Doubletten und ineinander enthaltenen Listen
     * befreit.
     *
     * @param listOfListsOfIntegers
     *            Die Liste mit den Listen von Integers.
     */
    public static void removeIntegerListsThatAreContainedInOtherListsFromListWithListsOfIntegers(
            List<List<Integer>> listOfLists) {
        CollectionsHelper.makeListDisjunct(listOfLists);

        List<Integer> indicesToDelete = new ArrayList<>();
        for (int index1 = 0; index1 < listOfLists.size() - 1; ++index1) {
            List<Integer> rowIndicesWithContent1 = listOfLists.get(index1);
            for (int index2 = 0; index2 < listOfLists.size(); ++index2) {
                List<Integer> rowIndicesWithContent2 = listOfLists.get(index2);
                if (CollectionsHelper.areAllElementsOfFirstAreInSecond(rowIndicesWithContent1,
                        rowIndicesWithContent2)) {
                    if (rowIndicesWithContent1.size() < rowIndicesWithContent2.size()
                            &&!indicesToDelete.contains(index1)) {
                        say("Entferne " + rowIndicesWithContent1 + " wegen "
                                + rowIndicesWithContent2 + " (1)");
                        indicesToDelete.add(index1);
                    }
                }
                else if (CollectionsHelper.areAllElementsOfFirstAreInSecond(rowIndicesWithContent2,
                        rowIndicesWithContent1)) {
                    if (rowIndicesWithContent2.size() < rowIndicesWithContent1.size()
                            && !indicesToDelete.contains(index2)) {
                        say("Entferne " + rowIndicesWithContent2 + " wegen "
                                + rowIndicesWithContent1 + " (2)");
                        indicesToDelete.add(index2);
                    }
                }
            }
        }
        CollectionsHelper.sortDescanding(indicesToDelete);
        for (int indexToDelete : indicesToDelete) {
            listOfLists.remove(indexToDelete);
        }
    }

    /**
     * Sortiert eine Liste von nicht leeren Listen von Integers nach zuerst deren erstem Element
     * (kleine Zahlen vor großen) und dann nach der Länge der Liste (kurze Listen vor langen).
     */
    public static void sortListOfIntegerListByFirstElementOfEachListAndLength(
            List<List<Integer>> listOfLists) {
        Collections.sort(listOfLists, new Comparator<List<Integer>>() {
            @Override
            public int compare(List<Integer> list1, List<Integer> list2) {
                int number1 = list1.get(0);
                int number2 = list2.get(0);
                if (number1 < number2) {
                    return -1;
                }
                if (number1 > number2) {
                    return 1;
                }
                int size1 = list1.size();
                int size2 = list2.size();
                if (size1 < size2) {
                    return -1;
                }
                if (size1 > size2) {
                    return 1;
                }
                return 0;
            }
        });
    }

    /** Erzeugt eine kürzere Liste (ist sie kurz genug, bleibt die Liste gleich). */
    public static <T> List<T> createShortList(List<T> list, int shorterSize) {
        List<T> shorterList = new ArrayList<>();

        int count = 0;
        for (T element : list) {
            ++count;
            if (count <= shorterSize) {
                shorterList.add(element);
            }
            else {
                break;
            }
        }

        return shorterList;
    }

    /**
     * Prüft, ob mindestens ein Element der übergebenen Liste mit dem übergebenen Anfang beginnt.
     */
    public static boolean startsAnyListElementWith(List<String> list, String start) {
        for (String element : list) {
            if (element.startsWith(start)) {
                return true;
            }
        }

        return false;
    }

    /** Entfernt alle leeren Elemente am Ende der Liste. */
    public static void pruneEmptyListElementsAtEnd(List<String> list) {
        boolean loop = true;
        while (loop) {
            if (list.isEmpty()) {
                loop = false;
            }
            else {
                int lastIndex = list.size() - 1;
                String lastArgument = list.get(lastIndex);
                if (lastArgument.isEmpty()) {
                    list.remove(lastIndex);
                    loop = true;
                }
                else {
                    loop = false;
                }
            }
        }
    }

    /** Gibt an, ob alle Elemente gleich sind. Wenn die Liste leer ist, wird false zurück gegeben. */
    public static boolean allListElementsAreEqual(List<String> list) {
        if (list.isEmpty()) {
            return false;
        }

        String firstElement = list.get(0);
        for (int index = 1; index < list.size(); ++index) {
            String element = list.get(index);
            if (!element.equals(firstElement)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Gibt an, ob alle Elemente den gewünschten Text enthalten.
     *
     * Wenn die Liste leer ist, wird false zurück gegeben.
     */
    public static boolean allListElementsContain(List<String> list, String search) {
        if (list.isEmpty()) {
            return false;
        }

        for (String element : list) {
            if (!element.contains(search)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Gibt aus eine Liste von Integers den kleinsten Wert zurück.
     *
     * Ist die Liste leer, wird der übergebene Wert für eine leere Liste zurückgegeben.
     *
     * @param numbers
     *            Die Liste von Integers.
     * @param valueForEmpytList
     *            Der Wert der für eine leere Liste zurückgegeben werden soll.
     * @return Kleinster Wert der Liste oder valueForEmpytList im Falle einer leeren Liste.
     */
    public static int getSmallestValue(List<Integer> numbers, int valueForEmpytList) {
        if (numbers.isEmpty()) {
            return valueForEmpytList;
        }

        int smallestNumber = Integer.MAX_VALUE;
        for (int number : numbers) {
            if (smallestNumber > number) {
                smallestNumber = number;
            }
        }

        return smallestNumber;
    }

    /** Erzeugt aus der Liste mit Integer-Werten eine Liste mit Strings. */
    public static List<String> toStringList(List<Integer> intList) {
        List<String> stringList = new ArrayList<>();

        for (int intValue : intList) {
            String stringValue = Integer.toString(intValue);
            stringList.add(stringValue);
        }

        return stringList;
    }

    /**
     * Erzeugt die Liste der Elemente, die mit dem gewünschten Start beginnen.
     *
     * @param list
     *            Die zugrundeliegende Liste
     * @param start
     *            Der gewünschte Start der gesuchten Elemente.
     * @return Liste der Elemente, die mit dem gewünschten Start beginnen.
     */
    public static List<String> createListOfElementsStartingWith(List<String> list, String start) {
        List<String> startingList = new ArrayList<>();

        for (String value : list) {
            if (value.startsWith(start)) {
                startingList.add(value);
            }
        }

        return startingList;
    }

    /**
     * Gibt eine harmonisierte Liste zurück, bei der die Elemente ab der übergebenden Zeichenkette
     * durch ggf. Einfügen von Leerzeichen so ausgerichtet werden, dass diese Zeichenketten alle an
     * der gleichen Position stehen.
     *
     * Dies passiert aber nur mit dem ersten Vorkommen dieser Zeichenkette.
     *
     * @param list
     *            Die Liste mit den zu harmonisierenden Elementen.
     * @param toHarmoniseAt
     *            Die zeichenkette, an der die Elemente harmonisiert werden.
     * @return
     */
    public static List<String> harmoniseListElements(List<String> list, String toHarmoniseAt) {
        int maxIndex = -1;
        for (String element : list) {
            int index = element.indexOf(toHarmoniseAt);
            if (maxIndex < index) {
                maxIndex = index;
            }
        }

        if (maxIndex == -1) {
            return copyList(list);
        }

        List<String> harmonisedList = new ArrayList<>();
        for (String element : list) {
            int index = element.indexOf(toHarmoniseAt);
            if (index == -1 || index == maxIndex) {
                harmonisedList.add(element);
            }
            else {
                String front = element.substring(0, index);
                String middle = Text.multipleString(" ", maxIndex - index);
                String rear = element.substring(index);
                String harmonisedElement = front + middle + rear;
                harmonisedList.add(harmonisedElement);
            }
        }
        return harmonisedList;
    }

    /**
     * Erzeugt aus dem übergebenen Array ein Array, bei dem das Element mit dem übergebenen Index
     * fehlt.
     *
     * @param arrray
     *            Das zu verkleinernde Array.
     * @param indexToRemove
     *            Der Index des zu entfernenden Elements.
     * @return Verkleinertes Array
     */
    public static String[] createArrayWithoutElementAtIndex(String[] arrray, int indexToRemove) {
        List<String> list = CollectionsHelper.arrayToList(arrray);
        list.remove(indexToRemove);
        String[] shorterArray = CollectionsHelper.stringListToArray(list);
        return shorterArray;
    }

    /**
     * Prüft, ob der Text irgendeinen der Suchbegriffe beinhaltet.
     *
     * @param text
     *            Der Text.
     * @param searchWords
     *            Die zu suchenden Suchbegriffe.
     * @return Wahrheitswert, wahr genau dann, wenn mindestens einer der Suchbegriffe enthalten
     *         ist.
     */
    public static boolean containesAnyListElement(String text, List<String> searchWords) {
        for (String searchWord : searchWords) {
            if (text.contains(searchWord)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Zählt wie oft der der Text irgendeinen der Suchbegriffe beinhaltet.
     *
     * Dabei wird nicht ausgeschlossen, dass sich die Suchbegriffe nicht überlappen.
     *
     * @param text
     *            Der Text.
     * @param searchWords
     *            Die zu suchenden Suchbegriffe.
     * @return Die Anzahl  wie oft einer der Suchbegriffe enthalten ist.
     */
    public static int countNumberofContainedListElement(String text, List<String> searchWords) {
        int count = 0;

        for (String searchWord : searchWords) {
            count += Text.countPartInString(text, searchWord);
        }

        return count;
    }

    /**
     * Erzeugt aus einer Liste von Worten einen Liste von Worten, deren jeweils erstes Zeichen groß
     * und der Rest klein ist.
     */
    public static List<String> toFirstUpperCase(List<String> words) {
        List<String> firstUpperCaseWords = new ArrayList<>();

        for (String word : words) {
            String firstUpperCaseWord = Text.toFirstUpperCase(word);
            firstUpperCaseWords.add(firstUpperCaseWord);
        }

        return firstUpperCaseWords;
    }

    /**
     * Gibt das längste Element der Liste zurück. Wenn die Liste leer ist, wird der leere String
     * zurückgegeben.
     */
    public static String getLongestListElement(List<String> list) {
        String longestValue = "";

        for (String value : list) {
            if (longestValue.length() < value.length()) {
                longestValue = value;
            }
        }

        return longestValue;
    }

    private static void say(String message) {
        if (DEBUG) {
            Text.say(message);
        }
    }

}
