de.codecamp.messages.shared.messageformat.DefaultMessageFormatSupport Maven / Gradle / Ivy
package de.codecamp.messages.shared.messageformat;
import java.text.ChoiceFormat;
import java.text.DateFormat;
import java.text.Format;
import java.text.MessageFormat;
import java.text.NumberFormat;
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.Calendar;
import java.util.Date;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import de.codecamp.messages.MessageKeyWithArgs;
/**
* Implements {@link MessageFormatSupport} for {@link MessageFormat Java's MessageFormat}.
*
* If changes are made to the checked types, this must be reflected in
* {@link de.codecamp.messages.runtime.DefaultMessageArgConverter}.
*/
public class DefaultMessageFormatSupport
implements
MessageFormatSupport
{
public static final String ID = "classic";
private static final Set NUMBER_FORMAT_TYPE_NAME_MAPPING = new HashSet<>();
static
{
NUMBER_FORMAT_TYPE_NAME_MAPPING.addAll(Arrays.asList(Number.class.getName()));
}
private static final Set DATE_FORMAT_TYPE_NAME_MAPPING = new HashSet<>();
static
{
DATE_FORMAT_TYPE_NAME_MAPPING.addAll(
Arrays.asList(Date.class.getName(), Calendar.class.getName(), Number.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()));
}
@Override
public boolean supportsFormat(String messageFormat)
{
return ID.equals(messageFormat);
}
@Override
public boolean hasArgNameSupport()
{
return false;
}
@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 = "{" + 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(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<>();
int usedArgCount = 0;
if (argTypes != null && argTypes.length > 0)
{
Deque messageParts = new ArrayDeque<>();
messageParts.addLast(message);
while (!messageParts.isEmpty())
{
String messagePart = messageParts.removeFirst();
MessageFormat messageFormat;
try
{
messageFormat = new MessageFormat(messagePart);
}
catch (IllegalArgumentException ex)
{
errors.add(String.format("The message is not a valid pattern: %s", ex.getMessage()));
return errors;
}
Format[] argFormats = messageFormat.getFormatsByArgumentIndex();
if (argFormats.length > usedArgCount)
usedArgCount = argFormats.length;
for (int argIndex = 0; argIndex < argFormats.length; argIndex++)
{
Format argFormat = argFormats[argIndex];
if (argFormat == null)
// no explicit format or not referenced in the first place
continue;
// is referenced index even declared?
if (argIndex >= argTypes.length)
continue;
if (argFormat instanceof ChoiceFormat)
{
for (Object f : ((ChoiceFormat) argFormat).getFormats())
messageParts.addLast((String) f);
}
String argTypeName = argTypes[argIndex];
// ChoiceFormat is also a NumberFormat
if (argFormat instanceof NumberFormat)
{
if (!argTypeChecker.isCompatibleWith(argTypeName, NUMBER_FORMAT_TYPE_NAME_MAPPING))
{
errors.add(String.format(
"The message expects a number at index %d instead of the declared type: %s",
argIndex, argTypeName));
}
}
else if (argFormat instanceof DateFormat)
{
if (!argTypeChecker.isCompatibleWith(argTypeName, DATE_FORMAT_TYPE_NAME_MAPPING))
{
errors.add(String.format(
"The message expects a date at index %d instead of the declared type: %s",
argIndex, argTypeName));
}
}
}
}
}
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;
}
}