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

de.codecamp.messages.shared.messageformat.IcuMessageFormatSupport Maven / Gradle / Ivy

There is a newer version: 2.1.0
Show newest version
package de.codecamp.messages.shared.messageformat;

import static java.util.stream.Collectors.toSet;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Deque;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import com.ibm.icu.text.MessageFormat;
import com.ibm.icu.text.MessagePatternUtil;
import com.ibm.icu.text.MessagePatternUtil.ArgNode;
import com.ibm.icu.text.MessagePatternUtil.ComplexArgStyleNode;
import com.ibm.icu.text.MessagePatternUtil.MessageContentsNode;
import com.ibm.icu.text.MessagePatternUtil.MessageContentsNode.Type;
import com.ibm.icu.text.MessagePatternUtil.MessageNode;
import com.ibm.icu.text.MessagePatternUtil.VariantNode;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.CurrencyAmount;

import de.codecamp.messages.MessageKeyWithArgs;


/**
 * Implements {@link MessageFormatSupport} for {@link MessageFormat ICU's MessageFormat}.
 * 

* If changes are made to the checked types, this must be reflected in * {@link de.codecamp.messages.runtime.IcuMessageArgConverter}. */ public class IcuMessageFormatSupport implements MessageFormatSupport { public static final String ID = "icu"; /** * the supported Java types for each argument type of the formatter */ private static final SetMultimap TYPE_NAME_MAPPING = HashMultimap.create(); static { TYPE_NAME_MAPPING.putAll("number", Arrays.asList(Number.class.getName(), CurrencyAmount.class.getName(), // javax.money types "javax.money.MonetaryAmount")); TYPE_NAME_MAPPING.putAll("date", Arrays.asList(Date.class.getName(), Calendar.class.getName(), Number.class.getName(), java.util.Calendar.class.getName(), // java.time types LocalDate.class.getName(), LocalTime.class.getName(), LocalDateTime.class.getName(), ZonedDateTime.class.getName(), OffsetTime.class.getName(), OffsetDateTime.class.getName(), Instant.class.getName())); TYPE_NAME_MAPPING.putAll("time", TYPE_NAME_MAPPING.get("date")); TYPE_NAME_MAPPING.putAll("spellout", Arrays.asList(Number.class.getName())); TYPE_NAME_MAPPING.putAll("ordinal", Arrays.asList(Number.class.getName())); TYPE_NAME_MAPPING.putAll("duration", Arrays.asList(Number.class.getName())); TYPE_NAME_MAPPING.putAll("plural", Arrays.asList(Number.class.getName())); TYPE_NAME_MAPPING.putAll("select", Arrays.asList(String.class.getName())); TYPE_NAME_MAPPING.putAll("choice", Arrays.asList(Number.class.getName())); } @Override public boolean supportsFormat(String messageFormat) { return IcuMessageFormatSupport.ID.equals(messageFormat) || DefaultMessageFormatSupport.ID.equals(messageFormat); } @Override public boolean hasArgNameSupport() { return true; } @Override public String formatArgType(String argType) { if ("javax.money.MonetaryAmount".equals(argType)) { return "currency"; } else { return MessageFormatSupport.super.formatArgType(argType); } } @Override public List getArgInsertOptions(MessageKeyWithArgs key) { List result = new ArrayList<>(); String[] argTypes = key.getArgTypes(); String[] argNames = key.getArgNames(); for (int i = 0; i < argTypes.length; i++) { String label = argNames[i] + " : " + formatArgType(argTypes[i]); String reference; if (argNames[i] != null) reference = "{" + argNames[i] + "}"; else reference = "{" + i + "}"; result.add(new ArgInsert(label, reference)); } return result; } @Override public String createMessageBundleComment(MessageKeyWithArgs key) { String comment; if (key.hasArgs()) { String[] argTypes = key.getArgTypes(); String[] argNames = key.getArgNames(); StringBuilder messageArgComment = new StringBuilder(); messageArgComment.append("Arguments: "); boolean first = true; for (int i = 0; i < argTypes.length; i++) { if (first) first = false; else messageArgComment.append(" | "); messageArgComment.append(argNames[i]).append(":"); messageArgComment.append(formatArgType(argTypes[i])); messageArgComment.append(" -> "); messageArgComment.append("{").append(argNames[i]).append("}"); } comment = messageArgComment.toString(); } else { comment = null; } return comment; } @Override public List checkMessage(String message, String[] argTypes, String[] argNames, TypeChecker argTypeChecker) { List errors = new ArrayList<>(); MessageFormat mf; try { mf = new MessageFormat(message); } catch (IllegalArgumentException ex) { errors.add(String.format("The message is not a valid ICU pattern: %s", ex.getMessage())); return errors; } if (mf.usesNamedArguments()) { Set availableArgNames = Stream.of(argNames).filter(Objects::nonNull).collect(toSet()); Deque messageNodes = new ArrayDeque<>(); messageNodes.push(MessagePatternUtil.buildMessageNode(message)); while (!messageNodes.isEmpty()) { MessageNode messageNode = messageNodes.pop(); for (MessageContentsNode node : messageNode.getContents()) { if (node.getType() == Type.ARG) { ArgNode argNode = (ArgNode) node; if (argNode.getNumber() > -1) { errors.add("The message mixes named and indexed arguments."); continue; } ComplexArgStyleNode complexStyle = argNode.getComplexStyle(); if (complexStyle != null) { for (VariantNode variantNode : complexStyle.getVariants()) { messageNodes.push(variantNode.getMessage()); } } String messageArgName = argNode.getName(); if (!availableArgNames.contains(messageArgName)) { errors.add( String.format("The message uses an unavailable argument: %s", argNode.getName())); continue; } String typeName = argNode.getTypeName(); if (typeName != null) { String argType = null; for (int i = 0; i < argNames.length; i++) { if (messageArgName.equals(argNames[i])) argType = argTypes[i]; } if (argType != null) checkTypeName(typeName, argType, argTypeChecker, errors); } } } } } else { int maxIndex = -1; Deque messageNodes = new ArrayDeque<>(); messageNodes.push(MessagePatternUtil.buildMessageNode(message)); while (!messageNodes.isEmpty()) { MessageNode messageNode = messageNodes.pop(); for (MessageContentsNode node : messageNode.getContents()) { if (node.getType() == Type.ARG) { ArgNode argNode = (ArgNode) node; ComplexArgStyleNode complexStyle = argNode.getComplexStyle(); if (complexStyle != null) { for (VariantNode variantNode : complexStyle.getVariants()) { messageNodes.push(variantNode.getMessage()); } } if (argNode.getNumber() > maxIndex) maxIndex = argNode.getNumber(); String typeName = argNode.getTypeName(); if (typeName != null) { String argType = null; if (argTypes.length > argNode.getNumber()) argType = argTypes[argNode.getNumber()]; checkTypeName(typeName, argType, argTypeChecker, errors); } } } } int usedArgCount = maxIndex + 1; int argCount = argTypes == null ? 0 : argTypes.length; if (usedArgCount > argCount) { errors.add(String.format("The message uses more arguments (%d) than are declared (%d)", usedArgCount, argCount)); } } return errors; } /** * * @param typeName * the {@link ArgNode#getTypeName() type name} of the argument in the message * @param argType * the (Java) type of the declared message argument */ private void checkTypeName(String typeName, String argType, TypeChecker argTypeChecker, List errors) { Set expectedArgTypes = TYPE_NAME_MAPPING.get(typeName); if (!argTypeChecker.isCompatibleWith(argType, expectedArgTypes)) { errors.add( String.format("The message format type %s does not match the expected argument types: %s", typeName, StringUtils.join(expectedArgTypes, ","))); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy