/* * Copyright (C) 2013 David Sowerby * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package uk.q3c.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.helpers.MessageFormatter; import java.util.ArrayList; import java.util.List; /** * The native Java {@link java.text.MessageFormat} has some quirky behaviour especially when using the apostrophe * (single quote) character. The {@link MessageFormatter} from sl4j claims much faster performance, but expects the * argument parameters and arguments to be in the same order (this is perfectly reasonable for logging, but different * languages will require substitution in different orders). * <p/> * Neither is completely suited to I18N translation * <p/> * * @author David Sowerby 10 Feb 2013 */ public class MessageFormat { private static Logger log = LoggerFactory.getLogger(MessageFormat.class); /** * This method uses {@link MessageFormatter} for speed and resilience, but acts as an intermediary and takes a * pattern string of the format: * <p/> * <ul> * <em>this is a {1} pattern where the {0} can be in any {2}</i> * </ul> * and arguments * <ul> * <i>parameters, simple, order</em> * </ul> * will result in * <ul> * <i>this is a simple pattern where the parameters can be in any order</i> * </ul> * This is done by sorting the arguments into the same order as they are required by the pattern. No claims are * made * for efficiency or performance - {@link MessageFormatter} is fast, but this utility has not been optimised. This * method is deliberately not tolerant of errors in the pattern structure - substitution will simply not occur, and * the unmodified pattern returned. * <p/> * If you want to include a "{" in the output, simply escape it "\\{". This will escape the whole placeholder * <p/> * You can have any number of parameters, provided the numbering sequence is continuous, starts from zero, and is * matched by the same number of arguments. */ public static String format(String pattern, Object... arguments) { List<Integer> parameters = new ArrayList<>(); try { String strippedPattern = scanForParameters(pattern, parameters); Object[] sortedArguments = sortArguments(parameters, arguments, pattern); return MessageFormatter.arrayFormat(strippedPattern, sortedArguments) .getMessage(); } catch (Exception e) { return pattern; } } private static String scanForParameters(String pattern, List<Integer> parameters) { int i = 0; StringBuilder strippedPattern = new StringBuilder(); while (i < pattern.length()) { char c = pattern.charAt(i); // if the '{' has been escaped this moves the scan beyond it, thereby ignoring it if (c == '\\') { i++; c = pattern.charAt(i); if (c == '{') { strippedPattern.append('{'); i++; c = pattern.charAt(i); strippedPattern.append(c); } } else { strippedPattern.append(c); } // find an opening brace if (c == '{') { // find the closing '}' and extract StringBuilder placeholder = new StringBuilder(); boolean done = false; while (!done) { i++; c = pattern.charAt(i); if (c == '}') { parameters.add(Integer.valueOf(placeholder.toString())); strippedPattern.append(c); done = true; } else { placeholder.append(c); } } } i++; } return strippedPattern.toString(); } private static Object[] sortArguments(List<Integer> parameters, Object[] arguments, String pattern) { if (parameters.size() != arguments.length) { Object[] args = new Object[]{parameters.size(), arguments.length, pattern}; log.warn("Message pattern and arguments do not match, there are {} parameters in the pattern, " + "and {} arguments. The pattern is: '{}'", args); throw new RuntimeException(); } List<Object> sortedArguments = new ArrayList<>(); for (Integer i : parameters) { sortedArguments.add(arguments[i]); } return sortedArguments.toArray(); } }