Site icon JVM Advent

Beyond the Basics: Elevating Java String Handling with Custom Template Processors

Java 21 has introduced a groundbreaking feature called String Templates. This new feature, which is part of JEP 430, changes the way Java handles strings by combining literal text with dynamic expressions and template processors. This is perfect for situations where you need to use values that are calculated at runtime or entered by the user. String Templates revolutionize the way strings are handled in Java applications. In this article, we will take a closer look at String Templates.

Delving into Template Expressions

At the core of String Templates is a new syntax for Java called ‘template expressions’. This syntax enables efficient and secure string interpolation, going beyond simple string concatenation to transform structured text into various object types based on predefined rules.

For instance:

String userName = "Bazlur";
String greeting = STR."Welcome, \{userName}";
assert greeting.equals("Welcome, Bazlur"); // evaluates to true

The Role of STR Template Processor

The STR processor, a key component of Java’s string interpolation toolkit, skillfully replaces embedded expressions within templates, converting them into strings. For example, this capability could be particularly useful for creating structured HTML:

String pageTitle = "About Us";
String message = "Welcome to our site";
String htmlContent = STR."""
        <html>
          <head>
            <title>\{pageTitle}</title>
          </head>
          <body>
            <p>\{message}</p>
          </body>
        </html>
        """;

This example highlights the versatility of template expressions in producing dynamic and secure HTML content.

The FMT Template Processor: A Formatting Powerhouse

Complementing STR is FMT, a fellow template processor that boasts enhanced formatting capabilities. FMT’s distinguishing feature lies in its ability to interpret format specifiers similar to those found in java.util.Formatter. This trait makes FMT an ideal choice for generating structured and formatted outputs.

Consider this scenario:

record Building(String name, double length, double width) {
    double footprint() {
        return length * width;
    }
}

Building[] buildings = {
    new Building("Tower", 20.5, 15.75),
    new Building("Warehouse", 40.0, 22.3),
    new Building("Office", 30.1, 18.6),
};

String buildingTable = FMT."""
    Name             Length   Width    Footprint
    %-15s\{buildings[0].name} %7.2f\{buildings[0].length} %7.2f\{buildings[0].width}  %10.2f\{buildings[0].footprint()}
    %-15s\{buildings[1].name} %7.2f\{buildings[1].length} %7.2f\{buildings[1].width}  %10.2f\{buildings[1].footprint()}
    %-15s\{buildings[2].name} %7.2f\{buildings[2].length} %7.2f\{buildings[2].width}  %10.2f\{buildings[2].footprint()}
    """;

Output: 

Name             Length   Width    Footprint
Tower             20.50   15.75      322.88
Warehouse         40.00   22.30      892.00
Office            30.10   18.60      559.86

This example effectively demonstrates FMT’s proficiency in handling intricate string outputs, such as those found in tabular presentations.

Embracing Custom Template Processors

Java 21 extends its capabilities by introducing custom template processors. This flexibility empowers developers to customize string manipulation according to their specific needs.

Consider the following example of a custom processor:

var TEMP = StringTemplate.Processor.of((StringTemplate st) -> {
    StringBuilder result = new StringBuilder();
    Iterator<String> fragmentIterator = st.fragments().iterator();
    for (Object value : st.values()) {
        result.append(fragmentIterator.next());
        result.append(value);
    }
    result.append(fragmentIterator.next());
    return result.toString();
});

double temperature = 36.5;
String healthStatus = TEMP."Body temperature: \{temperature}°C";

//Body temperature: 36.5°C

In this example, TEMP adeptly handles the combination of text fragments and dynamic values to produce a coherent string.

Let’s explore some additional examples:

Concatenating Strings with a Delimiter (DELIM):

This processor is designed to concatenate a collection of strings, such as elements from a list, using a specified delimiter like a comma.

var DELIM = StringTemplate.Processor.of((StringTemplate st) -> {
   String delimiter = ", ";
   var values = st.values();
   return values.stream()
           .flatMap(it -> {
               if (it instanceof List<?> item) {
                   return item.stream();
               } else return Stream.empty();
           }).map(String::valueOf)
           .collect(Collectors.joining(delimiter));
});

List<String> fruitNames = List.of("Apple", "Banana", "Cherry");
String fruits = DELIM."\{fruitNames}";
System.out.println(fruits);

//Apple, Banana, Cherry

The outcome is a well-formatted, comma-separated string, ideal for presenting lists in a clear and concise manner. Although such strings can be created using various techniques, defining a dedicated String processor offers the benefit of reusability. Once established, this processor can be effortlessly incorporated into any part of the source code, aligning with the DRY (Don’t Repeat Yourself) principle. This approach not only simplifies coding but also promotes consistency and efficiency throughout the application.

Formatting Dates (DATE_FORMAT):

This processor transforms LocalDate objects into formatted string representations, utilizing DateTimeFormatter.

var DATE_FORMAT = StringTemplate.Processor.of((StringTemplate st) -> {
   var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
   return st.values().stream()
           .map(value -> ((LocalDate) value).format(formatter))
           .collect(Collectors.joining(", "));
});

LocalDate date1 = LocalDate.of(2023, 3, 15);
LocalDate date2 = LocalDate.of(2023, 4, 20);
String formattedDates = DATE_FORMAT."\{date1} \{date2}";

System.out.println(formattedDates);
//2023-03-15, 2023-04-20

The output demonstrates the processor’s ability to convert date objects into a standardized, human-readable date format.

Building a JSON Object (JSON_BUILDER):

Aimed at creating JSON structures dynamically, this processor pairs each fragment with a value to construct a JSONObject.

 

var JSON_BUILDER = StringTemplate.Processor.of((StringTemplate st) -> {
   JSONObject json = new JSONObject();
   Iterator<Object> valueIterator = st.values().iterator();
   for (String key : st.fragments()) {
       if (valueIterator.hasNext()) {
           json.put(key.trim(), valueIterator.next());
       }
   }
   return json;
});

String product = "Laptop";
double price = 999.99;
JSONObject productJson = JSON_BUILDER."product: \{product}, price: \{price}";

System.out.println(productJson);
//{"product:":"Laptop",", price:":999.99}

This example illustrates the processor’s capability to efficiently generate JSON objects, which is useful in data interchange and API interactions.

Generating XML Elements (XML_GEN):
This processor is particularly adept at creating XML structures from Java objects, wrapping each value in XML tags.

var XML_GEN = StringTemplate.Processor.of((StringTemplate st) -> {
   var xml = new StringBuilder("<items>");
   var values = st.values();
   if (!values.isEmpty()) {
       if (values.getFirst() instanceof List<?> items) {
           items.forEach(value -> xml.append("<item>")
                   .append(value)
                   .append("</item>"));
       }
   }
   xml.append("</items>");
   return xml.toString();
});

List<String> book = List.of("Book", "Pen", "Notebook");
String items = XML_GEN."\{book}";

System.out.println(items);
//<items><item>Book</item><item>Pen</item><item>Notebook</item></items>

The output exemplifies the processor’s utility in generating structured XML content, which can be essential for data representation and communication in XML-based systems.

Note:

String Template is a preview feature. Here’s how you can enable and use preview features:

Compile with Preview Features: When compiling your Java code, use the --enable-preview flag with the javac command. For example:
javac –enable-preview –release 21 YourJavaFile.java

Run with Preview Features: Similarly, when running your Java application, include the --enable-preview flag with the java command. For example:
java –enable-preview YourJavaFile

IDE Support: If you’re using an Integrated Development Environment (IDE), ensure it supports the latest Java version. Most modern IDEs like IntelliJ IDEA, Eclipse, or Visual Studio Code have options to enable preview features in their project settings.

Conclusion

The introduction of String Templates marks a significant leap forward in Java’s string manipulation capabilities, providing developers with a powerful and versatile toolkit for crafting secure and efficient strings. This new feature is sure to have a transformative impact on Java programming practices.

Author: Bazlur Rahman

A N M Bazlur Rahman is a Software Engineer with over a decade of specialized experience in Java and related technologies. His expertise has been formally recognized through the prestigious title of Java Champion. Beyond his professional commitments, Mr. Rahman is deeply involved in community outreach and education. He is the founder and current moderator of the Java User Group in Bangladesh, where he has organized educational meetups and conferences since 2013. He was named Most Valuable Blogger (MVP) at DZone, one of the most recognized technology publishers in the world. Besides DZone, he is an editor for the Java Queue at InfoQ, another leading technology content publisher and conference organizer, and an editor at Foojay.io, a place for friends of OpenJDK. In addition, he has published five books about the Java programming language in Bengali; they were bestsellers in Bangladesh. He earned his bachelor’s degree from the Institute of Information Technology, University of Dhaka, Bangladesh, in Information Technology, majoring in Software Engineering. He currently lives in Toronto, Canada.

Exit mobile version