001/* 002 * Stallion Core: A Modern Web Framework 003 * 004 * Copyright (C) 2015 - 2016 Stallion Software LLC. 005 * 006 * This program is free software: you can redistribute it and/or modify it under the terms of the 007 * GNU General Public License as published by the Free Software Foundation, either version 2 of 008 * the License, or (at your option) any later version. This program is distributed in the hope that 009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 011 * License for more details. You should have received a copy of the GNU General Public License 012 * along with this program. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>. 013 * 014 * 015 * 016 */ 017 018package io.stallion.utils; 019 020import io.stallion.exceptions.UsageException; 021import io.stallion.reflection.PropertyUtils; 022import io.stallion.services.Log; 023 024import java.util.HashMap; 025import java.util.Map; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028 029import static io.stallion.utils.Literals.*; 030 031 032/** 033 * SimpleTemplating excepts a String with context tokens in the form of {object.attribute.attribute} 034 * You can populate the template with context objects. 035 * Then you render the template, and all tokens get replaced with the attribute values. 036 * 037 * For example: 038 * 039 * String result = new SimpleTemplate("My name is {user.displayName}") 040 * .put("user", new User().setDisplayName("Peter Pan") 041 * .render(); 042 * 043 * Result will equal: "My name is Peter Pan" 044 * 045 * 046 */ 047public class SimpleTemplate { 048 private static Pattern curlyPattern = Pattern.compile("\\{\\s*([\\w\\.]+)\\s*\\}"); 049 private static Pattern doubleCurlyPattern = Pattern.compile("\\{\\{\\s*([\\w\\.]+)\\s*\\}\\}"); 050 private static Pattern dollarPattern = Pattern.compile("\\$\\$\\s*([\\w\\.]+)\\s*\\$\\$"); 051 private Map<String, Object> context; 052 private String template; 053 private Boolean strict = false; 054 private MatchPattern pattern = MatchPattern.DOLLAR.SINGLE_CURLY; 055 056 public static enum MatchPattern { 057 DOUBLE_CURLY, 058 SINGLE_CURLY, 059 DOLLAR 060 } 061 062 public SimpleTemplate(String template) { 063 this.template = template; 064 this.context = new HashMap<>(); 065 } 066 067 068 public SimpleTemplate(String template, Map<String, Object> context) { 069 this(template, context, null); 070 } 071 072 public SimpleTemplate(String template, Map<String, Object> context, MatchPattern pattern) { 073 if (context == null) { 074 context = new HashMap<>(); 075 } 076 if (pattern != null) { 077 this.pattern = pattern; 078 } 079 this.context = context; 080 this.template = template; 081 } 082 083 public SimpleTemplate putAll(Map<String, Object> context) { 084 this.context.putAll(context); 085 return this; 086 } 087 088 public SimpleTemplate put(String key, Object val) { 089 this.context.put(key, val); 090 return this; 091 } 092 093 public String render() { 094 Matcher matcher; 095 if (pattern.equals(MatchPattern.DOLLAR)) { 096 matcher = dollarPattern.matcher(template); 097 } else if (pattern.equals(MatchPattern.DOUBLE_CURLY)) { 098 matcher = doubleCurlyPattern.matcher(template); 099 } else { 100 matcher = curlyPattern.matcher(template); 101 } 102 StringBuffer result = new StringBuffer(); 103 for(Object c: safeLoop(1000)) { 104 if (!matcher.find()) { 105 break; 106 } 107 String replacement = getReplacement(matcher); 108 matcher.appendReplacement(result, replacement); 109 } 110 matcher.appendTail(result); 111 return result.toString(); 112 } 113 114 private String getReplacement(Matcher matcher) { 115 String[] parts = matcher.group(1).split("\\."); 116 StringBuilder builder = new StringBuilder(); 117 Object thing = context; 118 for (String part: parts) { 119 Object oldThing = thing; 120 if (thing instanceof Map) { 121 thing = ((Map)thing).get(part); 122 } else { 123 thing = PropertyUtils.getProperty(thing, part); 124 } 125 if (thing == null) { 126 String msg = "In token " + matcher.group(1) + " attribute " + part + " is null."; 127 if (strict) { 128 throw new UsageException(msg); 129 } else { 130 Log.warn(msg); 131 return ""; 132 } 133 } 134 } 135 return thing.toString(); 136 } 137 138 public Boolean getStrict() { 139 return strict; 140 } 141 142 public void setStrict(Boolean strict) { 143 this.strict = strict; 144 } 145}