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

sirius.kernel.nls.Formatter Maven / Gradle / Ivy

/*
 * Made with all the love in the world
 * by scireum in Remshalden, Germany
 *
 * Copyright by scireum GmbH
 * http://www.scireum.de - [email protected]
 */

package sirius.kernel.nls;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import sirius.kernel.commons.Strings;

import java.util.List;
import java.util.Map;

/**
 * An alternative for MessageFormat which generates strings by replacing named parameters in a given template.
 * 

* A formatter is created for a given template string which contains named parameters like {@code ${param1}}. * Using one of the set methods, values for the parameters can be supplied. Calling {@code #format} * creates the output string. *

* Non string objects which are passed in as parameters, will be converted using {@link NLS#toUserString(Object)} *

* A formatter is neither thread safe nor intended for reuse. Instead a formatter is created, supplied with the * relevant parameters by chaining calls to set and then discarded after getting the result string via * format. *

* An example call might look like this: *

 * {@code
 *         System.out.println(
 *              Formatter.create("Hello ${programmer}")
 *                       .set("programmer, "Obi Wan")
 *                       .format());
 * }
 * 
*

* {@link NLS} uses this class by supplied translated patterns when calling {@link NLS#fmtr(String)}. * * @see NLS#fmtr(String) */ public class Formatter { private boolean urlEncode = false; private boolean smartFormat = false; private Map replacement = Maps.newTreeMap(); private String pattern; private String lang; /** * Use the static factory methods create to obtain a new instance. */ protected Formatter() { super(); } /** * Creates a new formatter with the given pattern and language. *

* The given language will be used when converting non-string parameters. * * @param pattern specifies the pattern to be used for creating the output * @param lang specifies the language used when converting non-string parameters. * @return this for fluently calling set methods. */ public static Formatter create(String pattern, String lang) { Formatter result = new Formatter(); result.pattern = pattern; result.lang = lang; return result; } /** * Creates a new formatter with the given pattern. *

* Uses the currently active language when converting non-string parameters. * * @param pattern specifies the pattern to be used for creating the output * @return this for fluently calling set methods. */ public static Formatter create(String pattern) { Formatter result = new Formatter(); result.pattern = pattern; result.lang = NLS.getCurrentLang(); return result; } /** * Creates a new formatter with auto url encoding turned on. *

* Any parameters passed to this formatter will be automatically url encoded. * * @param pattern specifies the pattern to be used for creating the output * @return this for fluently calling set methods. */ public static Formatter createURLFormatter(String pattern) { Formatter result = new Formatter(); result.pattern = pattern; result.urlEncode = true; return result; } /** * Adds the replacement value to use for the given property. * * @param property the parameter in the template string which should be replaced * @param value the value which should be used as replacement * @return this to permit fluent method chains */ public Formatter set(String property, Object value) { setDirect(property, NLS.toUserString(value, lang), urlEncode); return this; } /** * Adds the replacement value to use for the given property, without url encoding the value. *

* Formatters created by #createURLFormatter perform automatic url conversion for all parameters. * Using this method however, disables url encoding for the given parameter and value. * * @param property the parameter in the template string which should be replaced * @param value the value which should be used as replacement * @return this to permit fluent method chains */ public Formatter setUnencoded(String property, Object value) { return setDirect(property, NLS.toUserString(value, lang), false); } /** * Sets the whole context as parameters in this formatter. *

* Calls #set for each entry in the given map. * * @param ctx a Map which provides a set of entries to replace. * @return this to permit fluent method chains */ public Formatter set(Map ctx) { if (ctx != null) { for (Map.Entry e : ctx.entrySet()) { set(e.getKey(), e.getValue()); } } return this; } /** * Directly sets the given string value for the given property. *

* Sets the given string as replacement value for the named parameter. The value will not be sent through * {@link NLS#toUserString(Object)} and therefore not trimmed etc. * * @param property the parameter in the template string which should be replaced * @param value the value which should be used as replacement * @param urlEncode determines if url encoding should be applied. If the parameter is set to false, * this method won't perform any url encoding, even if the formatter was created * using #createURLFormatter * @return this to permit fluent method chains */ public Formatter setDirect(String property, String value, boolean urlEncode) { replacement.put(property, urlEncode ? Strings.urlEncode(value) : value); return this; } /** * Generates the formatted string. *

* Applies all supplied replacement values on detected parameters formatted like {@code ${param}}. * * @return the template string with all parameters replaced for which a value was supplied. * @throws java.lang.IllegalArgumentException if the pattern is malformed */ public String format() { return format(false); } /** * Generates the formatted string using smart output formatting. *

* Applies all supplied replacement values on detected parameters formatted like {@code ${param}}. * Block can be formed using '[' and ']' a whole block is only output, if at least one replacement * was not empty. *

* Consider the pattern {@code [${salutation} ][${firstname}] ${lastname}}. This will create * Mr. Foo Bar if all three parameters are filled, but Mr. Bar if the first name is missing * or Foo Bar if the salutation is missing. * * @return the template string with all parameters replaced for which a value was supplied. * @throws java.lang.IllegalArgumentException if the pattern is malformed */ public String smartFormat() { return format(true); } /* * Keeps track of the current smart formatting block being parsed. */ private static class Block { StringBuilder output = new StringBuilder(); boolean replacementFound = false; int startIndex; } /* * Stack based implementation parsing parameterized strings with smart blocks. Each nested block will * result in one stack level. */ private String format(boolean smart) { List blocks = Lists.newArrayList(); Block currentBlock = new Block(); blocks.add(currentBlock); int index = 0; while (index < pattern.length()) { char current = pattern.charAt(index); if (current == '$' && pattern.charAt(index + 1) == '{') { index = performParameterReplacement(currentBlock, index); } else if (current == '[' && smart) { currentBlock = startBlock(blocks, index); } else if (current == ']' && smart) { currentBlock = endBlock(blocks, currentBlock, index); } else { currentBlock.output.append(current); } index++; } if (blocks.size() > 1) { throw new IllegalArgumentException(Strings.apply( "Unexpected end of pattern. Expected ']' for '[' at index %d in '%s'", currentBlock.startIndex + 1, pattern)); } else { return currentBlock.output.toString(); } } private int performParameterReplacement(Block currentBlock, int index) { index += 2; int keyStart = index; while (index < pattern.length() && pattern.charAt(index) != '}') { index++; } if (index >= pattern.length()) { throw new IllegalArgumentException(Strings.apply("Missing } for ${ started at index %d in '%s'", keyStart - 1, pattern)); } String key = pattern.substring(keyStart, index); String value = replacement.computeIfAbsent(key, s -> { throw new IllegalArgumentException(Strings.apply("Unknown value '%s' used at index %d in '%s'", key, keyStart - 1, pattern)); }); if (Strings.isFilled(value)) { currentBlock.output.append(value); currentBlock.replacementFound = true; } return index; } private Block startBlock(List blocks, int index) { Block currentBlock; currentBlock = new Block(); currentBlock.startIndex = index; blocks.add(currentBlock); return currentBlock; } private Block endBlock(List blocks, Block currentBlock, int index) { if (blocks.size() == 1) { throw new IllegalArgumentException(Strings.apply("Unexpected ']' at index %d in '%s'", index + 1, pattern)); } if (currentBlock.replacementFound) { Block next = blocks.get(blocks.size() - 2); next.output.append(currentBlock.output); next.replacementFound = true; } currentBlock = blocks.get(blocks.size() - 2); blocks.remove(blocks.size() - 1); return currentBlock; } @Override public String toString() { return format(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy