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);
}
}
}