package rfx.server.util.template; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Stream; import rfx.server.util.StringPool; import rfx.server.util.StringUtil; import rfx.server.util.Utils; import com.github.jknack.handlebars.Context; import com.github.jknack.handlebars.Handlebars; import com.github.jknack.handlebars.Helper; import com.github.jknack.handlebars.Options; /** * @author trieu.nguyen @tantrieuf31 * */ public class HandlebarsHelpers { static final String ParallelStream = "parallelStream"; public static void register(Handlebars handlebars){ handlebars.registerHelper("doIf", doIfHelper); handlebars.registerHelper("doif", doIfHelper); handlebars.registerHelper("ifHasData", ifHasDataHelper); handlebars.registerHelper("forEach",forEachHelper); handlebars.registerHelper("foreach",forEachHelper); handlebars.registerHelper("eachInMap",eachInMapHelper); handlebars.registerHelper("base64Decode",base64DecodeHelper); handlebars.registerHelper("base64Encode",base64EncodeHelper); handlebars.registerHelper("randomInteger",randomIntegerHelper); handlebars.registerHelper("ifExist", ifExistHelper ); handlebars.registerHelper("ifListHasData", ifListHasDataHelper); handlebars.registerHelper("ifCond", ifCondHelper); } public static final String OPERATOR_EQUALS = "=="; public static final String OPERATOR_NOT_EQUALS = "!="; public static final String OPERATOR_LARGER_THAN = ">"; public static final String OPERATOR_LARGER_THAN_OR_EQUAL = ">="; public static final String OPERATOR_LESS_THAN = "<"; public static final String OPERATOR_LESS_THAN_OR_EQUAL = "<="; public static final String OPERATOR_NotInList = "NotInList"; public static final String OPERATOR_InList = "InList"; static boolean applyIf(Object param0, Object param1, String operator) throws IOException{ boolean rs = false; switch (operator) { case OPERATOR_EQUALS: { rs = param0.equals(param1); break; } case OPERATOR_NOT_EQUALS: { rs = ! param0.equals(param1); break; } case OPERATOR_LARGER_THAN: { int p0 = StringUtil.safeParseInt(param0); int p1 = StringUtil.safeParseInt(param1); rs = (p0 > p1); break; } case OPERATOR_LARGER_THAN_OR_EQUAL: { int p0 = StringUtil.safeParseInt(param0); int p1 = StringUtil.safeParseInt(param1); System.out.println(p0+ " >= "+ p1); rs = (p0 >= p1); break; } case OPERATOR_LESS_THAN: { int p0 = StringUtil.safeParseInt(param0); int p1 = StringUtil.safeParseInt(param1); rs = (p0 < p1); break; } case OPERATOR_LESS_THAN_OR_EQUAL: { int p0 = StringUtil.safeParseInt(param0); int p1 = StringUtil.safeParseInt(param1); rs = (p0 <= p1); break; } default: break; } //System.out.println(param0 + " " + param1 + " " + operator + " rs: " + rs); return rs; } /** * */ static Helper<Object> ifHasDataHelper = new Helper<Object>() { @Override public CharSequence apply(Object param0, Options options) throws IOException { if(StringUtil.isNullOrEmpty(param0)){ return options.inverse(this); } return options.fn(this); } }; /** * */ static Helper<Object> ifListHasDataHelper = new Helper<Object>() { @Override public CharSequence apply(Object param0, Options options) throws IOException { if(param0 != null){ List list = (List) param0; return (list.size()>0 ) ? options.fn(this) : options.inverse(this); } return options.inverse(this); } }; /** * Sample: {{#doIf tracking '==' "yes" }} the block text {{/doIf}} */ static Helper<Object> doIfHelper = new Helper<Object>() { @Override public CharSequence apply(Object param0, Options options) throws IOException { if(param0 != null){ Object[] toks = options.params; int len = toks.length; if(len < 2){ if(param0.getClass().isPrimitive()){ if(param0 instanceof Boolean){ return Boolean.parseBoolean(param0.toString()) ? options.fn(this) : options.inverse(this); } else if(param0 instanceof Integer){ return Integer.parseInt(param0.toString()) > 0 ? options.fn(this) : options.inverse(this); } else if(param0 instanceof Long){ return Long.parseLong(param0.toString()) > 0l ? options.fn(this) : options.inverse(this); } else if(param0 instanceof Double){ return Double.parseDouble(param0.toString()) > 0.0f ? options.fn(this) : options.inverse(this); } } //the param0 != null and with no operator, just return true return options.fn(this); } String operator = options.param(0); Object param1 = options.param(1); boolean rs = applyIf(param0, param1, operator); if(len >= 6){ int i = 2; while (i < len) { String logicOperator = String.valueOf(toks[i]); Object p0 = toks[i+1]; String compareOperator = String.valueOf(toks[i+2]); Object p1 = toks[i+3]; if(logicOperator.equals("&&")){ rs = rs && applyIf(p0, p1, compareOperator); } else if(logicOperator.equals("||")){ rs = rs || applyIf(p0, p1, compareOperator); } i+=4; } } if(rs){ return options.fn(this); } } return options.inverse(this); } }; /** * sample: {{#ifExist data }} {{data}} {{/ifExist}} */ static Helper<Object> ifExistHelper = new Helper<Object>() { @Override public CharSequence apply(Object param0, Options options) throws IOException { if(StringUtil.isNotEmpty(param0)){ return (! param0.toString().equals("0")) ? options.fn(this) : options.inverse(this); } return options.inverse(this); } }; /** * sample: {{#ifCond websiteUrl "NotInList" "zing.vn; mp3.zing.vn; forum.zing.vn" }} the block text {{/ifCond}} */ static Helper<Object> ifCondHelper = new Helper<Object>() { @Override public CharSequence apply(Object param0, Options options) throws IOException { if(param0 != null){ String operator = options.param(0); String param1 = options.param(1); String[] items; switch (operator) { case OPERATOR_EQUALS: return (param0.equals(param1)) ? options.fn(this) : options.inverse(this); case OPERATOR_NOT_EQUALS: return (! param0.equals(param1)) ? options.fn(this) : options.inverse(this); case OPERATOR_NotInList: { items = param1.split(StringPool.SEMICOLON); for (String item : items) { if(param0.equals(item.trim())){ return options.inverse(this); } } return options.fn(this); } case OPERATOR_InList: { items = param1.split(StringPool.SEMICOLON); for (String item : items) { if(param0.equals(item.trim())){ return options.fn(this); } } return options.inverse(this); } default: break; } } return options.inverse(this); } }; /** * sample: {{#randomInteger}}{{/randomInteger}} */ static Helper<Object> randomIntegerHelper = new Helper<Object>() { @Override public CharSequence apply(Object param0, Options options) throws IOException { int min = 1; int max = Integer.MAX_VALUE; if(StringUtil.isNotEmpty(param0)){ min= StringUtil.safeParseInt(param0+"",1); } if(options.params.length == 1){ max = options.param(0); } int r = Utils.randInt(min, max); return String.valueOf(r); } }; /** * */ static Helper<Object> eachInMapHelper = new Helper<Object>() { @Override public CharSequence apply(Object param0, Options options) throws IOException { if(param0 instanceof Map){ @SuppressWarnings("unchecked") Map<String,Object> map = (Map<String, Object>) param0; StringBuilder out = new StringBuilder(); map.forEach(new BiConsumer<String,Object>() { @Override public void accept(String key, Object value) { Map<String,Object> context = new HashMap<String, Object>(2); context.put(StringPool.KEY, key); context.put(StringPool.VALUE, value); try { String s = options.fn(context).toString(); out.append(s); } catch (Exception e) {} context.clear(); } }); return out.toString(); } return StringPool.BLANK; } }; /** * sample: <br> * {{#forEach infos "e" }} {{e.order}} {{e.data}} {{isNotLastItem}} {{/forEach}} */ static Helper<List<Object>> forEachHelper = new Helper<List<Object>>() { @Override public CharSequence apply(List<Object> list, Options options) throws IOException { if(list == null){ return StringPool.BLANK; } int len = options.params.length; String itemKey = len > 0 ? options.param(0) : "item"; boolean parallelStream = len > 1 ? options.param(1).equals(ParallelStream) : false; StringBuilder out = new StringBuilder(); AtomicInteger index = new AtomicInteger(0); final int lastIndex = list.size() - 1; Stream<Object> stream; if(parallelStream){ stream = list.parallelStream(); } else { stream = list.stream(); } stream.forEach(new Consumer<Object>() { @Override public void accept(Object object) { int i = index.get(); Context context = Context.newContext(object); context.data(itemKey, object); context.data("index", i); context.data("isNotLastItem", i < lastIndex); try { String s = options.fn(context).toString(); out.append(s); } catch (Exception e) {} finally { context.destroy(); } index.incrementAndGet(); } }); return out.toString(); } }; /** * Sample: {{#base64Decode "SmF2YSA4IGlzIGNvb2wgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2U=" }}{{/base64Decode}} => Java 8 is cool programming language */ static Helper<String> base64DecodeHelper = new Helper<String>() { @Override public CharSequence apply(String s, Options options) throws IOException { if(StringUtil.isNotEmpty(s)){ return StringUtil.base64StringDecode(s); } return StringPool.BLANK; } }; /** * Sample: {{#base64Encode "Java 8 is cool programming language" }}{{/base64Encode}} => SmF2YSA4IGlzIGNvb2wgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2U= */ static Helper<String> base64EncodeHelper = new Helper<String>() { @Override public CharSequence apply(String s, Options options) throws IOException { if(StringUtil.isNotEmpty(s)){ return StringUtil.base64StringEncode(s); } return StringPool.BLANK; } }; }