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

goja.core.kits.StrTemplateKit Maven / Gradle / Ivy

The newest version!
// Copyright (c) 2003-2014, Jodd Team (jodd.org). All Rights Reserved.

package goja.core.kits;

import goja.core.StringPool;

import java.util.Map;

/**
 * Parser for string macro templates. On parsing, macro values in provided string are resolved and
 * replaced with real values. Once set, one string template parser can be reused for parsing, even
 * using different macro resolvers.
 */
public class StrTemplateKit {

    public static final String  DEFAULT_MACRO_START = "${";
    // ---------------------------------------------------------------- properties
    public static final String  DEFAULT_MACRO_END   = "}";
    protected           String  macroStart          = DEFAULT_MACRO_START;
    protected           String  macroEnd            = DEFAULT_MACRO_END;
    protected           boolean replaceMissingKey   = true;
    protected String missingKeyReplacement;
    protected boolean resolveEscapes = true;
    protected char    escapeChar     = '\\';
    protected boolean parseValues;

    /**
     * Finds first occurrence of a substring in the given source but within limited range [start,
     * end). It is fastest possible code, but still original String.indexOf(String, int)
     * is much faster (since it uses char[] value directly) and should be used when no range is
     * needed.
     *
     * @param src        source string for examination
     * @param sub        substring to find
     * @param startIndex starting index
     * @param endIndex   ending index
     * @return index of founded substring or -1 if substring not found
     */
    public static int indexOf(String src, String sub, int startIndex, int endIndex) {
        if (startIndex < 0) {
            startIndex = 0;
        }
        int srclen = src.length();
        if (endIndex > srclen) {
            endIndex = srclen;
        }
        int sublen = sub.length();
        if (sublen == 0) {
            return startIndex > srclen ? srclen : startIndex;
        }

        int total = endIndex - sublen + 1;
        char c = sub.charAt(0);
        mainloop:
        for (int i = startIndex; i < total; i++) {
            if (src.charAt(i) != c) {
                continue;
            }
            int j = 1;
            int k = i + 1;
            while (j < sublen) {
                if (sub.charAt(j) != src.charAt(k)) {
                    continue mainloop;
                }
                j++;
                k++;
            }
            return i;
        }
        return -1;
    }

    /**
     * Creates commonly used {@link StrTemplateKit.MacroResolver} that resolved macros in the
     * provided map.
     */
    public static MacroResolver createMapMacroResolver(final Map map) {
        return new MacroResolver() {
            public String resolve(String macroName) {
                Object value = map.get(macroName);

                if (value == null) {
                    return null;
                }

                return value.toString();
            }
        };
    }

    public boolean isReplaceMissingKey() {
        return replaceMissingKey;
    }

    /**
     * Specifies if missing keys should be resolved at all, true by default. If
     * false missing keys will be left as it were, i.e. they will not be replaced.
     */
    public void setReplaceMissingKey(boolean replaceMissingKey) {
        this.replaceMissingKey = replaceMissingKey;
    }

    public String getMissingKeyReplacement() {
        return missingKeyReplacement;
    }

    /**
     * Specifies replacement for missing keys. If null exception will be thrown.
     */
    public void setMissingKeyReplacement(String missingKeyReplacement) {
        this.missingKeyReplacement = missingKeyReplacement;
    }

    public boolean isResolveEscapes() {
        return resolveEscapes;
    }

    /**
     * Specifies if escaped values should be resolved. In special usecases, when the same string has
     * to be processed more then once, this may be set to false so escaped values
     * remains.
     */
    public void setResolveEscapes(boolean resolveEscapes) {
        this.resolveEscapes = resolveEscapes;
    }

    public String getMacroStart() {
        return macroStart;
    }

    /**
     * Defines macro start string.
     */
    public void setMacroStart(String macroStart) {
        this.macroStart = macroStart;
    }

    public String getMacroEnd() {
        return macroEnd;
    }

    /**
     * Defines macro end string.
     */
    public void setMacroEnd(String macroEnd) {
        this.macroEnd = macroEnd;
    }

    public char getEscapeChar() {
        return escapeChar;
    }

    /**
     * Defines escape character.
     */
    public void setEscapeChar(char escapeChar) {
        this.escapeChar = escapeChar;
    }

    // ---------------------------------------------------------------- parse

    public boolean isParseValues() {
        return parseValues;
    }

    /**
     * Defines if macro values has to be parsed, too. By default, macro values are returned as they
     * are.
     */
    public void setParseValues(boolean parseValues) {
        this.parseValues = parseValues;
    }

    // ---------------------------------------------------------------- resolver

    /**
     * Parses string template and replaces macros with resolved values.
     */
    public String parse(String template, MacroResolver macroResolver) {
        StringBuilder result = new StringBuilder(template.length());

        int i = 0;
        int len = template.length();

        int startLen = macroStart.length();
        int endLen = macroEnd.length();

        while (i < len) {
            int ndx = template.indexOf(macroStart, i);
            if (ndx == -1) {
                result.append(i == 0 ? template : template.substring(i));
                break;
            }

            // check escaped
            int j = ndx - 1;
            boolean escape = false;
            int count = 0;

            while ((j >= 0) && (template.charAt(j) == escapeChar)) {
                escape = !escape;
                if (escape) {
                    count++;
                }
                j--;
            }
            if (resolveEscapes) {
                result.append(template.substring(i, ndx - count));
            } else {
                result.append(template.substring(i, ndx));
            }
            if (escape) {
                result.append(macroStart);
                i = ndx + startLen;
                continue;
            }

            // find macros end
            ndx += startLen;
            int ndx2 = template.indexOf(macroEnd, ndx);
            if (ndx2 == -1) {
                throw new IllegalArgumentException(
                        "Invalid template, unclosed macro at: " + (ndx - startLen));
            }

            // detect inner macros, there is no escaping
            int ndx1 = ndx;
            while (ndx1 < ndx2) {
                int n = indexOf(template, macroStart, ndx1, ndx2);
                if (n == -1) {
                    break;
                }
                ndx1 = n + startLen;
            }

            String name = template.substring(ndx1, ndx2);

            // find value and append
            Object value;
            if (missingKeyReplacement != null || !replaceMissingKey) {
                try {
                    value = macroResolver.resolve(name);
                } catch (Exception ignore) {
                    value = null;
                }

                if (value == null) {
                    if (replaceMissingKey) {
                        value = missingKeyReplacement;
                    } else {
                        value = template.substring(ndx1 - startLen, ndx2 + 1);
                    }
                }
            } else {
                value = macroResolver.resolve(name);
                if (value == null) {
                    value = StringPool.EMPTY;
                }
            }

            if (ndx == ndx1) {
                String stringValue = value.toString();
                if (parseValues) {
                    if (stringValue.contains(macroStart)) {
                        stringValue = parse(stringValue, macroResolver);
                    }
                }
                result.append(stringValue);
                i = ndx2 + endLen;
            } else {
                // inner macro
                template = template.substring(0, ndx1 - startLen) + value.toString() + template.substring(
                        ndx2 + endLen);
                len = template.length();
                i = ndx - startLen;
            }
        }
        return result.toString();
    }

    /**
     * Macro value resolver.
     */
    public interface MacroResolver {
        /**
         * Resolves macro value for macro name founded in string template. null values will
         * be replaced with empty strings.
         */
        String resolve(String macroName);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy