❝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!