| /* |
| ******************************************************************************* |
| * Copyright (C) 2010-2011, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ******************************************************************************* |
| * created on: 2010aug21 |
| * created by: Markus W. Scherer |
| */ |
| |
| package com.ibm.icu.dev.demo.messagepattern; |
| |
| import java.io.IOException; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import com.ibm.icu.text.MessagePattern; |
| import com.ibm.icu.text.MessagePattern.ArgType; |
| import com.ibm.icu.text.MessagePattern.Part; |
| import com.ibm.icu.util.Freezable; |
| |
| /** |
| * Mini message formatter for a small subset of the ICU MessageFormat syntax. |
| * Supports only string substitution and select formatting. |
| * @author Markus Scherer |
| * @since 2010-aug-21 |
| */ |
| public final class MiniMessageFormatter implements Freezable<MiniMessageFormatter> { |
| public MiniMessageFormatter() { |
| this.msg=new MessagePattern(); |
| } |
| |
| public MiniMessageFormatter(MessagePattern msg) { |
| this.msg=(MessagePattern)msg.clone(); |
| } |
| |
| public MiniMessageFormatter(String msg) { |
| this.msg=new MessagePattern(msg); |
| } |
| |
| public MiniMessageFormatter applyPattern(String msg) { |
| this.msg.parse(msg); |
| return this; |
| } |
| |
| public String getPatternString() { |
| return msg.getPatternString(); |
| } |
| |
| public boolean hasNamedArguments() { |
| return msg.hasNamedArguments(); |
| } |
| |
| public boolean hasNumberedArguments() { |
| return msg.hasNumberedArguments(); |
| } |
| |
| /** |
| * Formats the parsed message with positional arguments. |
| * Supports only string substitution (e.g., {3}) and select format. |
| * @param dest gets the formatted message appended |
| * @param args positional arguments |
| * @return dest |
| */ |
| public Appendable format(Appendable dest, Object... args) { |
| if(msg.hasNamedArguments()) { |
| throw new IllegalArgumentException( |
| "Formatting message with named arguments using positional argument values."); |
| } |
| format(0, dest, args, null); |
| return dest; |
| } |
| |
| public static final String format(String msg, Object... args) { |
| return new MiniMessageFormatter(msg).format(new StringBuilder(2*msg.length()), args).toString(); |
| } |
| |
| public Appendable format(Appendable dest, Map<String, Object> argsMap) { |
| if(msg.hasNumberedArguments()) { |
| throw new IllegalArgumentException( |
| "Formatting message with numbered arguments using named argument values."); |
| } |
| format(0, dest, null, argsMap); |
| return dest; |
| } |
| |
| public static final String format(String msg, Map<String, Object> argsMap) { |
| return new MiniMessageFormatter(msg).format(new StringBuilder(2*msg.length()), argsMap).toString(); |
| } |
| |
| private int format(int msgStart, Appendable dest, Object[] args, Map<String, Object> argsMap) { |
| try { |
| String msgString=msg.getPatternString(); |
| int prevIndex=msg.getPart(msgStart).getLimit(); |
| for(int i=msgStart+1;; ++i) { |
| Part part=msg.getPart(i); |
| Part.Type type=part.getType(); |
| int index=part.getIndex(); |
| dest.append(msgString, prevIndex, index); |
| if(type==Part.Type.MSG_LIMIT) { |
| return i; |
| } |
| if(type==Part.Type.SKIP_SYNTAX || type==Part.Type.INSERT_CHAR) { |
| prevIndex=part.getLimit(); |
| continue; |
| } |
| assert type==Part.Type.ARG_START : "Unexpected Part "+part+" in parsed message."; |
| int argLimit=msg.getLimitPartIndex(i); |
| ArgType argType=part.getArgType(); |
| part=msg.getPart(++i); |
| Object arg; |
| if(args!=null) { |
| try { |
| arg=args[part.getValue()]; // args[ARG_NUMBER] |
| } catch(IndexOutOfBoundsException e) { |
| throw new IndexOutOfBoundsException( |
| "No argument at index "+part.getValue()); |
| } |
| } else { |
| arg=argsMap.get(msg.getSubstring(part)); // args[ARG_NAME] |
| if(arg==null) { |
| throw new IndexOutOfBoundsException( |
| "No argument for name "+msg.getSubstring(part)); |
| } |
| } |
| String argValue=arg.toString(); |
| ++i; |
| if(argType==ArgType.NONE) { |
| dest.append(argValue); |
| } else if(argType==ArgType.SELECT) { |
| // Similar to SelectFormat.findSubMessage(). |
| int subMsgStart=0; |
| for(;; ++i) { // (ARG_SELECTOR, message) pairs until ARG_LIMIT |
| part=msg.getPart(i++); |
| if(part.getType()==Part.Type.ARG_LIMIT) { |
| assert subMsgStart!=0; // The parser made sure this is the case. |
| break; |
| // else: part is an ARG_SELECTOR followed by a message |
| } else if(msg.partSubstringMatches(part, argValue)) { |
| // keyword matches |
| subMsgStart=i; |
| break; |
| } else if(subMsgStart==0 && msg.partSubstringMatches(part, "other")) { |
| subMsgStart=i; |
| } |
| i=msg.getLimitPartIndex(i); |
| } |
| format(subMsgStart, dest, args, argsMap); |
| } else { |
| throw new UnsupportedOperationException("Unsupported argument type "+argType); |
| } |
| prevIndex=msg.getPart(argLimit).getLimit(); |
| i=argLimit; |
| } |
| } catch(IOException e) { // Appendable throws IOException |
| throw new RuntimeException(e); // We do not want a throws clause. |
| } |
| } |
| |
| /** |
| * Presents an array of (String, Object) pairs as a Map. |
| * Only for temporary use for formatting with named arguments. |
| */ |
| public static Map<String, Object> mapFromNameValuePairs(Object[] args) { |
| HashMap<String, Object> argsMap = new HashMap<String, Object>(); |
| for(int i=0; i<args.length; i+=2) { |
| argsMap.put((String)args[i], args[i+1]); |
| } |
| return argsMap; |
| } |
| |
| public MiniMessageFormatter cloneAsThawed() { |
| // TODO Auto-generated method stub |
| return null; |
| } |
| |
| public MiniMessageFormatter freeze() { |
| msg.freeze(); |
| return this; |
| } |
| |
| public boolean isFrozen() { |
| return msg.isFrozen(); |
| } |
| |
| private final MessagePattern msg; |
| } |