public static final String CSV_TRIMMED_SEPARATOR = CSV_SEPARATOR.trim();
 public static final Charset FILES_ENCODING = StandardCharsets.UTF_8;
 public static final String IDENTIFIER_SEPARATOR = " > ";
 public static final String IGNORE_MARK = "#";
 public static final String TAB = "   ";
 protected static final int ZERO_TO_ONE_DECIMAL_MAX_CHARS = 1
        + DECIMALS_FORMAT.getMinimumIntegerDigits()
        + DECIMALS_FORMAT.getMaximumFractionDigits();
 public static String escapeCsv(
        String str) {

    if (str.contains(CSV_TRIMMED_SEPARATOR) || str.contains(CSV_QUOTATION_MARK)) {

        return CSV_QUOTATION_MARK
                + str.replace(CSV_QUOTATION_MARK, CSV_QUOTATION_MARK + CSV_QUOTATION_MARK)
                + CSV_QUOTATION_MARK;

    } else {

        return str;

    }

}
 public static String formatDecimal(
        Double decimal) {

    if (decimal == null) {
        return NULL_VALUE;
    }

    return DECIMALS_FORMAT.format(decimal);

}
 public static String hyphens(
        int n) {

    return repeatString("-", n);

}
 public static int maxLength(
        Collection<String> strings) {

    int maxLength = 0;

    for (String string : strings) {
        if (string != null && string.length() > maxLength) {
            maxLength = string.length();
        }
    }

    return maxLength;

}
 public static <T> String prepareIdentifier(
        T identifier,
        int truncateLength) {

    String preparedIdentifier = null;

    if (identifier == null) {
        preparedIdentifier = NULL_IDENTIFIER;
    } else {
        preparedIdentifier = identifier.toString();
    }

    preparedIdentifier = preparedIdentifier.trim();

    if (truncateLength > 0 && preparedIdentifier.length() > truncateLength) {
        preparedIdentifier = preparedIdentifier.substring(0, truncateLength);
    }

    return preparedIdentifier;

}
 public static <T> List<String> prepareIdentifiers(
        Collection<T> identifiers,
        int truncateLength) {

    List<String> preparedIdentifiers = new ArrayList<>(identifiers.size());

    for (T identifier : identifiers) {
        String preparedIdentifier = prepareIdentifier(identifier, truncateLength);
        preparedIdentifiers.add(preparedIdentifier);
    }

    return preparedIdentifiers;

}
 public static String spaces(
        int n) {

    return repeatString(" ", n);

}
 protected enum AnsiCodeColors {

    /**
     * Red font color.
     */
    RED_FONT(31),

    /**
     * Green font color.
     */
    GREEN_FONT(32),

    /**
     * Blue font color.
     */
    BLUE_FONT(34),

    /**
     * Reset color.
     */
    RESET(0);

    /**
     * Color number.
     */
    private int number;

    /**
     * Color code.
     */
    private String code;

    /**
     * Constructor.
     * 
     * @param number Color number.
     */
    AnsiCodeColors(
            int number) {

        this.number = number;
        this.code = Character.toString(UNICODE_CTRL)
                + Character.toString(ANSI_CODE_START)
                + Integer.toString(number)
                + Character.toString(ANSI_CODE_COLOR_END);

    }

    /**
     * @return The color number.
     */
    protected int getNumber() {
        return number;
    }

    /**
     * @return The color code.
     */
    protected String getCode() {
        return code;
    }

    /**
     * Removes all ANSI code colors from a string.
     * 
     * @param string A string.
     * @return The string without any ANSI code colors.
     */
    protected static String remove(
            String string) {

        return ANSI_CODE_COLOR_PATTERN.matcher(string).replaceAll("");

    }

}
 public enum SimilarityTypes {

    /**
     * Similarity of a hash to other hashes.
     */
    SIMILARITY("Similarity"),

    /**
     * Similarity of other hashes to a hash.
     */
    REVERSE_SIMILARITY("Reverse"),

    /**
     * Maximum similarity between Similarity and Reverse.
     */
    MAXIMUM("Maximum"),

    /**
     * Minimum similarity between Similarity and Reverse.
     */
    MINIMUM("Minimum"),

    /**
     * Arithmetic mean between Similarity and Reverse, (Similarity * Reverse) / 2.
     */
    ARITHMETIC_MEAN("ArithMean"),

    /**
     * Geometric mean between Similarity and Reverse, sqrt(Similarity * Reverse).
     */
    GEOMETRIC_MEAN("GeomMean");

    /**
     * Similarity type name.
     */
    private String name;

    /**
     * Constructor.
     * 
     * @param name Similarity type name.
     */
    SimilarityTypes(
            String name) {

        this.name = name;

    }

    /**
     * @return The similarity type name.
     */
    public String getName() {

        return name;

    }

    /**
     * @return A list with all the similarity types names.
     */
    public static List<String> names() {

        SimilarityTypes[] similarityTypes = SimilarityTypes.values();
        List<String> similarityTypesNames = new ArrayList<>(similarityTypes.length);

        for (SimilarityTypes similarityType : similarityTypes) {
            similarityTypesNames.add(similarityType.name);
        }

        return similarityTypesNames;

    }

}

import static com.github.s3curitybug.similarityuniformfuzzyhash.ToStringUtils.CSV_TRIMMED_SEPARATOR;
import static com.github.s3curitybug.similarityuniformfuzzyhash.ToStringUtils.FILES_ENCODING;
import static com.github.s3curitybug.similarityuniformfuzzyhash.ToStringUtils.IDENTIFIER_SEPARATOR;
import static com.github.s3curitybug.similarityuniformfuzzyhash.ToStringUtils.IGNORE_MARK;
import static com.github.s3curitybug.similarityuniformfuzzyhash.ToStringUtils.TAB;
import static com.github.s3curitybug.similarityuniformfuzzyhash.ToStringUtils.ZERO_TO_ONE_DECIMAL_MAX_CHARS;
import static com.github.s3curitybug.similarityuniformfuzzyhash.ToStringUtils.escapeCsv;
import static com.github.s3curitybug.similarityuniformfuzzyhash.ToStringUtils.formatDecimal;
import static com.github.s3curitybug.similarityuniformfuzzyhash.ToStringUtils.hyphens;
import static com.github.s3curitybug.similarityuniformfuzzyhash.ToStringUtils.maxLength;
import static com.github.s3curitybug.similarityuniformfuzzyhash.ToStringUtils.prepareIdentifier;
import static com.github.s3curitybug.similarityuniformfuzzyhash.ToStringUtils.prepareIdentifiers;
import static com.github.s3curitybug.similarityuniformfuzzyhash.ToStringUtils.spaces;
import org.apache.commons.io.output.FileWriterWithEncoding;
import org.fusesource.jansi.AnsiConsole;
import com.github.s3curitybug.similarityuniformfuzzyhash.ToStringUtils.AnsiCodeColors;
import com.github.s3curitybug.similarityuniformfuzzyhash.UniformFuzzyHash.SimilarityTypes;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

    }

    /**
     * Writes a map of identified Uniform Fuzzy Hashes into a text file.
     * 
     * @param <T> Identifiers type.
     * @param hashes Map of identified Uniform Fuzzy Hashes.
     * @param file The file to save the hashes.
     * @param append True to append the hashes at the end of the file. False to overwrite the file.
     * @throws IOException If an IOException occurs writing into the file.
     */
    public static <T> void saveHashesToTextFile(
            Map<T, UniformFuzzyHash> hashes,
            File file,
            boolean append)
            throws IOException {

        if (hashes == null) {
            throw new NullPointerException("Map of hashes is null.");
        }

        if (file == null) {
            throw new NullPointerException("File is null.");
        }

        if (file.exists() && !file.isFile()) {
            throw new IllegalArgumentException(String.format(
                    "%s is not a file.",
                    file.getName()));
        }

        try (PrintWriter writer = new PrintWriter(new FileWriterWithEncoding(
                file, FILES_ENCODING, append))) {

            Set<Entry<T, UniformFuzzyHash>> entries = hashes.entrySet();

            for (Entry<T, UniformFuzzyHash> entry : entries) {

                T identifier = entry.getKey();
                UniformFuzzyHash hash = entry.getValue();

                if (hash == null) {
                    writer.println();
                    continue;
                }

                String textLine = hashToTextLine(identifier, hash);
                writer.println(textLine);

            }

        }

    }

    /**
     * Loads a map of identified Uniform Fuzzy Hashes from a text file.
     * Lines starting by # are ignored.
     * 
     * @param file The file to load the hashes.
     * @return Map of identified Uniform Fuzzy Hashes.
     * @throws IOException IOException If an IOException occurs reading from the file.
     */
    public static Map<String, UniformFuzzyHash> loadHashesFromTextFile(
            File file)
            throws IOException {

        if (file == null) {
            throw new NullPointerException("File is null.");
        }

        if (!file.exists()) {
            throw new IllegalArgumentException(String.format(
                    "File %s does not exist.",
                    file.getName()));
        }

        if (!file.isFile()) {
            throw new IllegalArgumentException(String.format(
                    "%s is not a file.",
                    file.getName()));
        }

        Map<String, UniformFuzzyHash> hashes = new LinkedHashMap<>();

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(
                file), FILES_ENCODING))) {

            int lineNumber = 1;
            for (String line = reader.readLine(); line != null; line = reader.readLine()) {

                try {
                    rebuildHashFromTextLine(line, hashes);
                } catch (IllegalArgumentException illegalArgumentException) {
                    throw new IllegalArgumentException(String.format(
                            "File %s could not be parsed. Line number %d could not be parsed. %s",
                            file.getName(),
                            lineNumber,
                            illegalArgumentException.getMessage()));
                }

                lineNumber++;

            }

        }

        return hashes;

    }

    /**
     * Computes all the types of similarity between a Uniform Fuzzy Hash and a map of identified
     * Uniform Fuzzy Hashes.
     * 
     * @param <T> Identifiers type.
     * @param hash A Uniform Fuzzy Hash.
     * @param hashes Map of identified Uniform Fuzzy Hashes.
     * @return Map of identified similarities.
     */

    public static <T> Map<T, Map<SimilarityTypes, Double>> computeHashToHashesSimilarities(
