| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to you 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 org.apache.calcite.util; |
| |
| import com.google.common.collect.ImmutableList; |
| |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| /** |
| * String template. |
| * |
| * <p>It is extended from {@link java.text.MessageFormat} to allow parameters |
| * to be substituted by name as well as by position. |
| * |
| * <p>The following example, using MessageFormat, yields "Happy 64th birthday, |
| * Ringo!": |
| * |
| * <blockquote>MessageFormat f = |
| * new MessageFormat("Happy {0,number}th birthday, {1}!");<br> |
| * Object[] args = {64, "Ringo"};<br> |
| * System.out.println(f.format(args);</blockquote> |
| * |
| * <p>Here is the same example using a Template and named parameters: |
| * |
| * <blockquote>Template f = |
| * new Template("Happy {age,number}th birthday, {name}!");<br> |
| * Map<Object, Object> args = new HashMap<Object, Object>();<br> |
| * args.put("age", 64);<br> |
| * args.put("name", "Ringo");<br> |
| * System.out.println(f.format(args);</blockquote> |
| * |
| * <p>Using a Template you can also use positional parameters: |
| * |
| * <blockquote>Template f = |
| * new Template("Happy {age,number}th birthday, {name}!");<br> |
| * Object[] args = {64, "Ringo"};<br> |
| * System.out.println(f.format(args);</blockquote> |
| * |
| * <p>Or a hybrid; here, one argument is specified by name, another by position: |
| * |
| * <blockquote>Template f = |
| * new Template("Happy {age,number}th birthday, {name}!");<br> |
| * Map<Object, Object> args = new HashMap<Object, Object>();<br> |
| * args.put(0, 64);<br> |
| * args.put("name", "Ringo");<br> |
| * System.out.println(f.format(args);</blockquote> |
| */ |
| public class Template extends MessageFormat { |
| private final List<String> parameterNames; |
| |
| /** |
| * Creates a Template for the default locale and the |
| * specified pattern. |
| * |
| * @param pattern the pattern for this message format |
| * @throws IllegalArgumentException if the pattern is invalid |
| */ |
| public static Template of(String pattern) { |
| return of(pattern, Locale.getDefault()); |
| } |
| |
| /** |
| * Creates a Template for the specified locale and |
| * pattern. |
| * |
| * @param pattern the pattern for this message format |
| * @param locale the locale for this message format |
| * @throws IllegalArgumentException if the pattern is invalid |
| */ |
| public static Template of(String pattern, Locale locale) { |
| final List<String> parameterNames = new ArrayList<>(); |
| final String processedPattern = process(pattern, parameterNames); |
| return new Template(processedPattern, parameterNames, locale); |
| } |
| |
| private Template( |
| String pattern, List<String> parameterNames, Locale locale) { |
| super(pattern, locale); |
| this.parameterNames = ImmutableList.copyOf(parameterNames); |
| } |
| |
| /** |
| * Parses the pattern, populates the parameter names, and returns the |
| * pattern with parameter names converted to parameter ordinals. |
| * |
| * <p>To ensure that the same parsing rules apply, this code is copied from |
| * {@link java.text.MessageFormat#applyPattern(String)} but with different |
| * actions when a parameter is recognized. |
| * |
| * @param pattern Pattern |
| * @param parameterNames Names of parameters (output) |
| * @return Pattern with named parameters substituted with ordinals |
| */ |
| private static String process(String pattern, List<String> parameterNames) { |
| StringBuilder[] segments = new StringBuilder[4]; |
| for (int i = 0; i < segments.length; ++i) { |
| segments[i] = new StringBuilder(); |
| } |
| int part = 0; |
| boolean inQuote = false; |
| int braceStack = 0; |
| for (int i = 0; i < pattern.length(); ++i) { |
| char ch = pattern.charAt(i); |
| if (part == 0) { |
| if (ch == '\'') { |
| segments[part].append(ch); // jhyde: don't lose orig quote |
| if (i + 1 < pattern.length() |
| && pattern.charAt(i + 1) == '\'') { |
| segments[part].append(ch); // handle doubles |
| ++i; |
| } else { |
| inQuote = !inQuote; |
| } |
| } else if (ch == '{' && !inQuote) { |
| part = 1; |
| } else { |
| segments[part].append(ch); |
| } |
| } else if (inQuote) { // just copy quotes in parts |
| segments[part].append(ch); |
| if (ch == '\'') { |
| inQuote = false; |
| } |
| } else { |
| switch (ch) { |
| case ',': |
| if (part < 3) { |
| part += 1; |
| } else { |
| segments[part].append(ch); |
| } |
| break; |
| case '{': |
| ++braceStack; |
| segments[part].append(ch); |
| break; |
| case '}': |
| if (braceStack == 0) { |
| part = 0; |
| makeFormat(segments, parameterNames); |
| } else { |
| --braceStack; |
| segments[part].append(ch); |
| } |
| break; |
| case '\'': |
| inQuote = true; |
| // fall through, so we keep quotes in other parts |
| default: |
| segments[part].append(ch); |
| break; |
| } |
| } |
| } |
| if (braceStack == 0 && part != 0) { |
| throw new IllegalArgumentException( |
| "Unmatched braces in the pattern."); |
| } |
| return segments[0].toString(); |
| } |
| |
| /** |
| * Called when a complete parameter has been seen. |
| * |
| * @param segments Comma-separated segments of the parameter definition |
| * @param parameterNames List of parameter names seen so far |
| */ |
| private static void makeFormat( |
| StringBuilder[] segments, |
| List<String> parameterNames) { |
| final String parameterName = segments[1].toString(); |
| final int parameterOrdinal = parameterNames.size(); |
| parameterNames.add(parameterName); |
| segments[0].append("{"); |
| segments[0].append(parameterOrdinal); |
| final String two = segments[2].toString(); |
| if (two.length() > 0) { |
| segments[0].append(",").append(two); |
| } |
| final String three = segments[3].toString(); |
| if (three.length() > 0) { |
| segments[0].append(",").append(three); |
| } |
| segments[0].append("}"); |
| segments[1].setLength(0); // throw away other segments |
| segments[2].setLength(0); |
| segments[3].setLength(0); |
| } |
| |
| /** |
| * Formats a set of arguments to produce a string. |
| * |
| * <p>Arguments may appear in the map using named keys (of type String), or |
| * positional keys (0-based ordinals represented either as type String or |
| * Integer). |
| * |
| * @param argMap A map containing the arguments as (key, value) pairs |
| * @return Formatted string. |
| * @throws IllegalArgumentException if the Format cannot format the given |
| * object |
| */ |
| public String format(Map<Object, Object> argMap) { |
| Object[] args = new Object[parameterNames.size()]; |
| for (int i = 0; i < parameterNames.size(); i++) { |
| args[i] = getArg(argMap, i); |
| } |
| return format(args); |
| } |
| |
| /** |
| * Returns the value of the {@code ordinal}th argument. |
| * |
| * @param argMap Map of argument values |
| * @param ordinal Ordinal of argument |
| * @return Value of argument |
| */ |
| private Object getArg(Map<Object, Object> argMap, int ordinal) { |
| // First get by name. |
| String parameterName = parameterNames.get(ordinal); |
| Object arg = argMap.get(parameterName); |
| if (arg != null) { |
| return arg; |
| } |
| // Next by integer ordinal. |
| arg = argMap.get(ordinal); |
| if (arg != null) { |
| return arg; |
| } |
| // Next by string ordinal. |
| return argMap.get(ordinal + ""); |
| } |
| |
| /** |
| * Creates a Template with the given pattern and uses it |
| * to format the given arguments. This is equivalent to |
| * <blockquote> |
| * <code>{@link #of(String) Template}(pattern).{@link #format}(args)</code> |
| * </blockquote> |
| * |
| * @throws IllegalArgumentException if the pattern is invalid, |
| * or if an argument in the |
| * <code>arguments</code> array is not of the |
| * type expected by the format element(s) |
| * that use it. |
| */ |
| public static String formatByName( |
| String pattern, |
| Map<Object, Object> argMap) { |
| return Template.of(pattern).format(argMap); |
| } |
| |
| /** |
| * Returns the names of the parameters, in the order that they appeared in |
| * the template string. |
| * |
| * @return List of parameter names |
| */ |
| public List<String> getParameterNames() { |
| return parameterNames; |
| } |
| } |