martes, 6 de octubre de 2015

Java Freemarker Macro to convert from Multiline Template to Single Line Output

 Use case:

 Our output file is compound of multiple long lines. Each long line presents several conditions which make their definition complex.

 Likewise many other markup languages, we'd like to be able to define those conditions and all the fields that define the line in a more readable format.

 Solution:

 Turn multiline template definition into a single line final output with a reusable generic Freemarker's macro.

 Steps:

1. Download freemarker.jar Java Engine Template library from http://freemarker.org/ and include it in your application's classpath.

2. Define your reusable macro to convert convert multiple-line template to single line output. Define next code within macros_library.ftl file (it should be reachable on classpath):



<#macro compress_lines single_line=false leading_spaces=true trailing_spaces=false >
 <#local content><#nested></#local>
 <#assign replaceRegex = "" >
 <#if leading_spaces >
  <#if (replaceRegex?length > 0) >
   <#assign replaceRegex = replaceRegex + "|" >
  </#if>
  <#assign replaceRegex = replaceRegex + "^\\s+" >
 </#if>
 <#if trailing_spaces >
  <#if (replaceRegex?length > 0) >
   <#assign replaceRegex = replaceRegex + "|" >
  </#if>
  <#assign replaceRegex = replaceRegex + "\\s+$" >
 </#if>
 <#if single_line >
  <#if (replaceRegex?length > 0) >
   <#assign replaceRegex = replaceRegex + "|" >
  </#if>
  <#assign replaceRegex = replaceRegex + "\\n|\\r" >
 </#if>
 <#if (replaceRegex?length > 0) >
${ content?replace(replaceRegex, "", "rm") }<#else><#-- <== a line feed here would be sent to the output -->
  <#-- it doesn't make much sense to use this macro then -->
${ content }
 </#if>
</#macro>


So the name of this macro is compress_lines, and it has three parameters:

single_line. Whose default value is false. It means not to convert lines into single lines. For the convenience of this entry, we will send it as true, so multiline is converted into single line.

leading_spaces. Whose default value is true. If this parameter is set to true, macro will remove all the leading spaces on each line so your indentation serves only for readability purposes and are not included in the output.

trailing_spaces. Whose default value is false. If this parameter is set to true, macro will remove all trailing spaces on each line. This is not common, that's why its default value is set to false.

By combining all these three parameters to true, the result is a multiline-indented template, to be converted into a single line narrowed output (in our example it will be named x.ftl).



<#setting time_zone="America/Mexico_City">
<#include "macros_library.ftl" >

This is multiline content
that will be rendered as multiple lines
in the output because it's outside macro

<@compress_lines single_line=true leading_spaces=true trailing_spaces=true >

"${employee.birthDate?datetime?string("dd/MM/yyyy HH:mm:ss")}"

 ,"${employee}"
 
 ,"${position}"
 
 ,
  <#if employee.employeeDepartment?? && employee.employeeDepartment.name?? >
  "${employee.employeeDepartment.name}"
 </#if>
 ,[
 <#list employee.colleagues?keys as curColleagueName>
  "${curColleagueName}"
   <#sep>, </#sep>
 </#list>
 ]
 
</@compress_lines>



An example output would be similar to next. Let's suppose we have two employees, one without any department and one with a department. It won't matter the line feeds and identation, the output enclosed by the macro will be printed in a single line without spaces:



Output:
This is multiline content
that will be rendered as multiple lines
in the output because it's outside macro

"11/05/1981 12:34:51","Ricardo","Graphic Designer",,["Roberto","Noe"]
"23/08/1984 14:23:01","Valente","Project Manager","Software Factory",["Ismael","Tom"]



 3. Previous output was ran in a Java class like next to run Freemarker template:



package com.x;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

public class X {
 private Template template;

 public static void main(String[] args) {
  @SuppressWarnings("deprecation")
  Configuration cfg = new Configuration();
  cfg.setClassForTemplateLoading(X.class, "/");
  
  template = cfg.getTemplate("x.ftl");

  StringWriter logWriter = new StringWriter();
  
  List<Employee> employees = queryEmployees();
template.process(employees, logWriter); System.out.println("Output:"); System.out.println(logWriter.toString()); } }




Although I designed compress_lines macro to be as generic as possible, have a look at the code and check if you can improve it to suit further needs.

 This entry was adapted from real world application. If there's any typo that disallows this example to be executed it will be appreciated if you let me know.

* Source code was formatted with: http://hilite.me/

sábado, 26 de septiembre de 2015

Java: Convert Dates between TimeZones before Java 8

Use case:

Your server is hosted in country with a different time zone than the country your consumers reside and, of course, they are expecting your date-based methods to be expressed in their time zone.

You cannot upgrade to Java 8 and some methods are already created, you just can send a date, which is time zone unaware.

Solution:

Deceive your classes into believing current time is different than the real time by offsetting time in milliseconds according to the difference in time zones:



public class DateUtil {
  
 protected static Date convertToTimeZone(String timezone, Date date) {
  Calendar timeZonedCal = GregorianCalendar.getInstance(TimeZone.getTimeZone(timezone));
  timeZonedCal.setTime(date);
  
  Calendar localCal = GregorianCalendar.getInstance();
  
  localCal.set(
    timeZonedCal.get(Calendar.YEAR)
    , timeZonedCal.get(Calendar.MONTH)
    , timeZonedCal.get(Calendar.DAY_OF_MONTH)
    , timeZonedCal.get(Calendar.HOUR_OF_DAY)
    , timeZonedCal.get(Calendar.MINUTE)
    , timeZonedCal.get(Calendar.SECOND));
  
  return localCal.getTime();
 }

 // TRY IT:
 public static void main(String[] args) {
  
  System.out.println(convertToTimeZone("Asia/Singapore", new Date()));
  System.out.println(convertToTimeZone("America/New_York", new Date()));
 }
}


 This was used to deceive log4j 1 to rotate the files in an appropriate date, by extending RollingPolicyBase and converting current date into time-zoned date. I can share the code in another entry if someones is interested.


* Source code was formatted with: http://hilite.me/