Created
March 14, 2020 22:28
-
-
Save zmeeagain/3de7b59bc057be8f45efe4cace674479 to your computer and use it in GitHub Desktop.
Simple templates processor with example context.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package templates; | |
import java.util.Collections; | |
import java.util.Map; | |
import java.util.HashMap; | |
import javax.annotation.Nullable; | |
/** | |
* A map-backed dictionary, that skips null values. | |
*/ | |
public class TemplateContext { | |
private Map<String, Object> variables = new HashMap<>(); | |
public static TemplateContext from(String key, @Nullable Object value) { | |
TemplateContext context = new TemplateContext(); | |
if (value != null) { | |
context.put(key, value); | |
} | |
return context; | |
} | |
public static TemplateContext from(Map<String, Object> map) { | |
return new TemplateContext().putAll(map); | |
} | |
public boolean has(String key) { | |
return variables.containsKey(key); | |
} | |
@Nullable | |
public Object get(String key) { | |
return variables.get(key); | |
} | |
public TemplateContext put(String key, @Nullable Object value) { | |
if (value != null) { | |
variables.put(key, value); | |
} | |
return this; | |
} | |
public TemplateContext putAll(Map<String, Object> variables) { | |
for (Map.Entry<String, Object> entry : variables.entrySet()) { | |
if (entry.getValue() != null) { | |
this.variables.put(entry.getKey(), entry.getValue()); | |
} | |
} | |
return this; | |
} | |
public TemplateContext putAllFrom(TemplateContext context) { | |
this.variables.putAll(context.asMap()); | |
return this; | |
} | |
public Map<String, Object> asMap() { | |
return Collections.unmodifiableMap(variables); | |
} | |
public TemplateContext clear() { | |
variables.clear(); | |
return this; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package templates; | |
public final class TemplateContexts { | |
private TemplateContexts() {} | |
public static TemplateContext fromDeviceInfo(DeviceInfo info) { | |
return new TemplateContext() | |
.put("device-brand", info.getBrand()) | |
.put("device-device", info.getDevice()) | |
.put("device-fingerprint", info.getFingerprint()) | |
.put("device-manufacturer", info.getManufacturer()) | |
.put("device-model", info.getModel()) | |
.put("device-product", info.getProduct()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package templates; | |
import java.io.IOException; | |
import java.io.Writer; | |
import java.util.regex.MatchResult; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
import javax.annotation.Nullable; | |
/** | |
* Stateful, cannot be used from multiple threads. However, it can be used to process a series of | |
* templates to the same output, e.g. | |
* | |
* <blockquote> | |
* | |
* <pre> | |
* TemplateProcessor p = new TemplateProcessor(); | |
* p.putContext(context); | |
* Writer writer = ... | |
* p.process(template1, writer); | |
* p.putContext(addictionContext); | |
* p.process(template2, writer); | |
* </pre> | |
* | |
* </blockquote> | |
* | |
* Alternatively, if no out is supplied, the result will be returned as one string: | |
* | |
* <blockquote> | |
* | |
* <pre> | |
* TemplateProcessor p = new TemplateProcessor(); | |
* p.putContext(context); | |
* String result = p.process(template); | |
* </pre> | |
* | |
* </blockquote> | |
*/ | |
public class TemplateProcessor { | |
@SuppressWarnings("all" /* Android needs redundant escapes */) | |
private static final Pattern VAR_PATTERN = | |
Pattern.compile("\\$\\{(\\p{Alpha}[\\p{Graph}&&[^\\{\\}]]*)\\}"); | |
// If null, missing variables stay in processed templates, i.e. if var is missing in context, | |
// then ${var} will stay as ${var} in output. | |
@Nullable | |
private Function<String, String> missingHandler = null; | |
private TemplateContext context = new TemplateContext(); | |
private Matcher matcher; | |
private int appendPos = 0; | |
public TemplateProcessor putContext(TemplateContext context) { | |
this.context.putAllFrom(context); | |
return this; | |
} | |
public TemplateProcessor setContext(TemplateContext context) { | |
this.context = context; | |
return this; | |
} | |
public TemplateContext getContext() { | |
return context; | |
} | |
public TemplateProcessor omitMissing() { | |
return setMissingHandler(varName -> ""); | |
} | |
public TemplateProcessor leaveMissing() { | |
return setMissingHandler(null); | |
} | |
public TemplateProcessor replaceMissing(String replacement) { | |
return setMissingHandler(varName -> replacement); | |
} | |
public String process(String template) { | |
StringBuilder builder = new StringBuilder(); | |
try { | |
process(template, builder); | |
} catch (IOException e) { | |
// No IOException for StringBuilder | |
} | |
return builder.toString(); | |
} | |
public void process(String template, Writer out) throws IOException { | |
process(template, (Appendable) out); | |
} | |
public void process(String template, Appendable out) throws IOException { | |
appendPos = 0; // reset in case processor is repeatedly called | |
matcher = VAR_PATTERN.matcher(template); | |
while (matcher.find()) { | |
MatchResult matchResult = matcher.toMatchResult(); | |
String varPlaceholder = matchResult.group(); | |
String varName = matchResult.group(1); | |
@Nullable Object varValue = context.get(varName); | |
String replacement = getReplacement(varPlaceholder, varName, varValue); | |
appendReplacement(template, replacement, out); | |
} | |
appendRemaining(template, out); | |
} | |
private TemplateProcessor setMissingHandler(@Nullable Function<String, String> handler) { | |
this.missingHandler = handler; | |
return this; | |
} | |
private String getReplacement(String varPlaceholder, String varName, @Nullable Object varValue) { | |
if (varValue == null) { | |
return missingHandler == null ? varPlaceholder : missingHandler.apply(varName); | |
} | |
return String.valueOf(varValue); | |
} | |
private void appendReplacement(String template, String replacement, Appendable out) | |
throws IOException { | |
out.append(template.substring(appendPos, matcher.start())); | |
out.append(replacement); | |
appendPos = matcher.end(); | |
} | |
private void appendRemaining(String template, Appendable out) throws IOException { | |
out.append(template.substring(appendPos)); | |
} | |
public static void main(String[] args) { | |
System.out.println(new TemplateProcessor() | |
.setContext(TemplateContext.from("one", "oneval")) | |
.omitMissing() | |
.process("this ${one} and the ${other} one")); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment