blob: b032e6b2cfca507339e89670b2b18f378bbb62f0 [file] [log] [blame]
/*
*******************************************************************************
* 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.samples.text.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;
}