SerializationUtils.java

package com.renomad.minum.utils;

import com.renomad.minum.security.ForbiddenUseException;
import com.renomad.minum.security.Inmate;

import java.util.ArrayList;
import java.util.List;

public final class SerializationUtils {

    private SerializationUtils() {
        // not meant to be constructed.
    }

    /**
     * This is a helper that will encode the values you give it
     * in preparation for storage in a database file.
     * <p>
     *     The values will be encoded in URL-encoding (see {@link StringUtils#encode(String)})
     *     and concatenated together with pipe-symbol "|" delimiters.
     * </p>
     * <p>
     *     <em>Please note</em>: You need to keep track of value order,
     *     and making sure all the values are accounted for.
     * </p>
     * <p>
     *     For example, see how this is used in {@link Inmate#serialize()}
     * </p>
     */
    public static String serializeHelper(Object... values) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < values.length-1; i++) {
            String value = values[i] == null ? null : values[i].toString();
            sb.append(StringUtils.encode(value)).append("|");
        }
        // append the last value with no pipe symbol afterwards
        String lastValue = values[values.length - 1] == null ? null : values[values.length - 1].toString();
        sb.append(StringUtils.encode(lastValue));
        return sb.toString();
    }

    /**
     * Splits up a string based on a pipe character.  See {@link #tokenizer(String, char, int)}
     * <p>
     *     This method is intended to be used as part of the database.  See
     *     the package "com.renomad.minum.database"
     * </p>
     * <p>
     *     For an example, see how this is used in {@link Inmate#deserialize(String)}
     * </p>
     * @param serializedText the string we are splitting into tokens
     */
    public static List<String> deserializeHelper(String serializedText) {
        /*
         * As a general precaution, loops throughout the system have
         * safety limits in place.  In this case, it would be unexpected
         * to have databases of type {@link com.renomad.minum.database.DbData} that
         * have this many fields.
         */
        int maximumDatabasePartitionsAllowed = 200;
        return tokenizer(serializedText, '|', maximumDatabasePartitionsAllowed).stream().map(StringUtils::decode).toList();
    }

    /**
     * Splits up a string into tokens.
     *
     * @param serializedText the string we are splitting up
     * @param delimiter the character acting as a boundary between sections
     * @param maxTokens the maximum tokens allowable.  Probably smart to include a number
     *                  here, since otherwise you could get into some infinite loops.
     * @return a list of strings.  If the delimiter is not found, we will just return the whole string
     */
    public static List<String> tokenizer(String serializedText, char delimiter, int maxTokens) {
        final var resultList = new ArrayList<String>();
        var currentPlace = 0;
        for(int i = 0; ; i++) {
            if (i >= maxTokens) {
                throw new ForbiddenUseException("Asked to split content into too many partitions in the tokenizer.  Current max: " + maxTokens);
            }
            final var nextDelimiterIndex = serializedText.indexOf(delimiter, currentPlace);
            if (nextDelimiterIndex == -1) {
                // if we don't see any delimiters ahead, grab the rest of the text from our current place
                resultList.add(serializedText.substring(currentPlace));
                break;
            }
            resultList.add(serializedText.substring(currentPlace, nextDelimiterIndex));
            currentPlace = nextDelimiterIndex + 1;
        }

        return resultList;
    }

}