--- /dev/null
+package net.pterodactylus.sone.template;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import net.pterodactylus.util.template.Part;
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.util.template.TemplateException;
+import net.pterodactylus.util.template.TemplateParser;
+
+import freenet.support.io.Closer;
+
+import com.google.common.base.Charsets;
+
+/**
+ * {@link Template} implementation that can be reloaded from the filesystem.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class FilesystemTemplate extends Template {
+
+ private final String filename;
+ private final AtomicReference<LastLoadedTemplate> lastTemplate = new AtomicReference<LastLoadedTemplate>();
+ private final TemplateContext initialContext = new TemplateContext();
+ private final List<Part> parts = new ArrayList<Part>();
+
+ public FilesystemTemplate(String filename) {
+ this.filename = filename;
+ }
+
+ @Override
+ public TemplateContext getInitialContext() {
+ loadTemplate();
+ return initialContext;
+ }
+
+ private void loadTemplate() {
+ File templateFile = new File(filename);
+ if (!templateFile.exists()) {
+ throw new TemplateFileNotFoundException(filename);
+ }
+ if (templateWasLoaded() && !templateFileHasBeenModifiedAfterLoading(templateFile)) {
+ return;
+ }
+ InputStream templateInputStream = null;
+ Reader templateReader = null;
+ try {
+ templateInputStream = new FileInputStream(templateFile);
+ templateReader = new InputStreamReader(templateInputStream, Charsets.UTF_8);
+ Template template = TemplateParser.parse(templateReader);
+ lastTemplate.set(new LastLoadedTemplate(template));
+ template.getInitialContext().mergeContext(initialContext);
+ for (Part part : parts) {
+ template.add(part);
+ }
+ } catch (FileNotFoundException e) {
+ throw new TemplateFileNotFoundException(filename);
+ } finally {
+ Closer.close(templateReader);
+ Closer.close(templateInputStream);
+ }
+ }
+
+ private boolean templateWasLoaded() {
+ return lastTemplate.get() != null;
+ }
+
+ private boolean templateFileHasBeenModifiedAfterLoading(File templateFile) {
+ return templateFile.lastModified() > lastTemplate.get().getLoadTime();
+ }
+
+ @Override
+ public void add(Part part) {
+ loadTemplate();
+ parts.add(part);
+ lastTemplate.get().getTemplate().add(part);
+ }
+
+ @Override
+ public void render(TemplateContext templateContext, Writer writer) throws TemplateException {
+ loadTemplate();
+ lastTemplate.get().getTemplate().render(templateContext, writer);
+ }
+
+ @Override
+ public Iterator<Part> iterator() {
+ loadTemplate();
+ return lastTemplate.get().getTemplate().iterator();
+ }
+
+ @Override
+ public int getLine() {
+ loadTemplate();
+ return lastTemplate.get().getTemplate().getLine();
+ }
+
+ @Override
+ public int getColumn() {
+ loadTemplate();
+ return lastTemplate.get().getTemplate().getColumn();
+ }
+
+ private static class LastLoadedTemplate {
+
+ private final Template template;
+ private final long loadTime = System.currentTimeMillis();
+
+ private LastLoadedTemplate(Template template) {
+ this.template = template;
+ }
+
+ public Template getTemplate() {
+ return template;
+ }
+
+ public long getLoadTime() {
+ return loadTime;
+ }
+
+ }
+
+ /**
+ * Exception that signals that a template file could not be found.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ public static class TemplateFileNotFoundException extends RuntimeException {
+
+ public TemplateFileNotFoundException(String filename) {
+ super(filename);
+ }
+
+ }
+
+}