Comparison operators for Strings

❝Comparison is the end of happiness and the beginning of dissatisfaction❞

—Søren Kierkegaard

FreshMarker allows you to overload a wide variety of operators for your own model types. These operators are multiplication, addition, negation and comparison operators. For strings, the addition operator + has so far been overloaded to realize a string concatenation. In this blog post, we want to implement comparison operators for strings.

To implement a comparison operator, it is sufficient to overwrite the relational method for your own primitive model types.

@Override
public TemplatePrimitive<?> relational(TokenType operator, TemplatePrimitive<?> operand, ProcessContext context) {
  TemplateString rightValue = (TemplateString) operand;
  return compareValues(operator, getValue().compareTo(rightValue.getValue()));
}

A first implementation for the TemplateString class uses the String#compareTo method to compare the current string with the other operand. With these few lines of code, strings can now be compared in the template.

'ABC' < 'ZZTop'

However, this implementation is a little unspectacular for a blog post, so we want to improve the possibilities of the comparison operators a little more. Java offers improved comparison options for String instances via the Collator class. The strength property of a Collator determines the degree of difference that is considered significant in comparisons. Four strengths are available: PRIMARY, SECONDARY, TERTIARY and IDENTICAL. The exact assignment of the strengths to the language features depends on the respective Locale.

We improve our implementation by adding the possibility to switch from simple String comparison to Collator comparison. To do this, we use a new FreshMarker feature SystemFeature.LOCALE_SENSITIVE_STRING_COMPARE.

@Override
public TemplatePrimitive<?> relational(TokenType operator, TemplatePrimitive<?> operand, ProcessContext context) {
  TemplateString rightValue = (TemplateString) operand;
  if (context.getFeatureSet().isEnabled(LOCALE_SENSITIVE_STRING_COMPARE)) {
    return compareValues(operator, compareWithCollator(operand, context));
  }
  return compareValues(operator, getValue().compareTo(rightValue.getValue()));
}

If the feature is enabled, we call the compareWithCollator method. In this method, the Collator is used for the comparison.

private int compareWithCollator(TemplateString rightValue, ProcessContext context) {
  Collator collator = Collator.getInstance(context.getLocale());
  context.getFeatureSet().getConfigured(LOCALE_SENSITIVE_STRING_COMPARE).map(Integer.class::cast).ifPresent(collator::setStrength);
  return collator.compare(getValue(), rightValue.getValue());
}

In the first line, the Collator is created with the current Locale from the ProcessContext. The strength is then set on the Collator if it was set via the Configuration. At the end, the String instances are compared with the Collator#compare method.

new Configuration().builder().with(SystemFeature.LOCALE_SENSITIVE_STRING_COMPARE, Collator.PRIMARY)

The above line configures a TemplateBuilder for use with a Collator that uses the strength Collator.PRIMARY. For the locale GERMAN, this means that the comparison 'jens' > 'JENS' is no longer true, but false. With this configuration the comparison is performed case-insensitive and no longer case-sensitive.

The new String Comparison Operators are part of the next release FreshMarker 2.0.0. Stay tuned!

Leave a Comment