package com.github.mustachejava; import com.github.mustachejava.codes.PartialCode; import com.github.mustachejava.util.GuardException; import com.github.mustachejava.util.InternalArrayList; import com.github.mustachejava.util.Wrapper; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicLong; /** * This allows you to automatically defer evaluation of partials. By default * it generates HTML but you can override that behavior. */ public class DeferringMustacheFactory extends DefaultMustacheFactory { public static final Object DEFERRED = new Object(); public DeferringMustacheFactory() { } public DeferringMustacheFactory(String resourceRoot) { super(resourceRoot); } public DeferringMustacheFactory(File fileRoot) { super(fileRoot); } private static class Deferral { final long id; final Future<Object> future; Deferral(long id, Future<Object> future) { this.id = id; this.future = future; } } public static class DeferredCallable implements Callable<String> { private List<Deferral> deferrals = new ArrayList<>(); public void add(Deferral deferral) { deferrals.add(deferral); } @Override public String call() throws Exception { StringBuilder sb = new StringBuilder(); for (Deferral deferral : deferrals) { Object o = deferral.future.get(); if (o != null) { writeDeferral(sb, deferral, o); } } return sb.toString(); } } @Override public MustacheVisitor createMustacheVisitor() { final AtomicLong id = new AtomicLong(0); return new DefaultMustacheVisitor(this) { @Override public void partial(TemplateContext tc, final String variable) { TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine()); final Long divid = id.incrementAndGet(); list.add(new PartialCode(partialTC, df, variable) { Wrapper deferredWrapper; @Override public Writer execute(Writer writer, final List<Object> scopes) { final Object object = get(scopes); final DeferredCallable deferredCallable = getDeferred(scopes); if (object == DEFERRED && deferredCallable != null) { try { writeTarget(writer, divid); writer.append(appended); } catch (IOException e) { throw new MustacheException("Failed to write", e, tc); } // Make a copy of the scopes so we don't change them List<Object> scopesCopy = new InternalArrayList<>(scopes); deferredCallable.add( new Deferral(divid, getExecutorService().submit(() -> { try { StringWriter sw = new StringWriter(); partial.execute(sw, scopesCopy).close(); return sw.toString(); } catch (IOException e) { throw new MustacheException("Failed to writer", e, tc); } }))); return writer; } else { return appendText(partial.execute(writer, scopes)); } } private DeferredCallable getDeferred(List<Object> scopes) { try { if (deferredWrapper == null) { deferredWrapper = getObjectHandler().find("deferred", scopes); } return (DeferredCallable) deferredWrapper.call(scopes); } catch (GuardException e) { deferredWrapper = null; return getDeferred(scopes); } } }); } }; } protected void writeTarget(Writer writer, Long divid) throws IOException { writer.append("<div id=\""); writer.append(divid.toString()); writer.append("\"></div>"); } protected static void writeDeferral(StringBuilder sb, Deferral deferral, Object o) { sb.append("<script>document.getElementById(\""); sb.append(deferral.id); sb.append("\").innerHTML=\""); sb.append(o.toString().replace("<", "<").replace("\"", "\\\"").replace("\n", "\\n")); sb.append("\";</script>"); } }