Merge branch 'release-0.9.6'
[Sone.git] / src / main / java / net / pterodactylus / sone / template / FilesystemTemplate.java
1 package net.pterodactylus.sone.template;
2
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.FileNotFoundException;
6 import java.io.InputStream;
7 import java.io.InputStreamReader;
8 import java.io.Reader;
9 import java.io.Writer;
10 import java.util.ArrayList;
11 import java.util.Iterator;
12 import java.util.List;
13 import java.util.concurrent.atomic.AtomicReference;
14
15 import net.pterodactylus.util.template.Part;
16 import net.pterodactylus.util.template.Template;
17 import net.pterodactylus.util.template.TemplateContext;
18 import net.pterodactylus.util.template.TemplateException;
19 import net.pterodactylus.util.template.TemplateParser;
20
21 import freenet.support.io.Closer;
22
23 import com.google.common.base.Charsets;
24
25 /**
26  * {@link Template} implementation that can be reloaded from the filesystem.
27  *
28  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
29  */
30 public class FilesystemTemplate extends Template {
31
32         private final String filename;
33         private final AtomicReference<LastLoadedTemplate> lastTemplate = new AtomicReference<LastLoadedTemplate>();
34         private final TemplateContext initialContext = new TemplateContext();
35         private final List<Part> parts = new ArrayList<Part>();
36
37         public FilesystemTemplate(String filename) {
38                 this.filename = filename;
39         }
40
41         @Override
42         public TemplateContext getInitialContext() {
43                 loadTemplate();
44                 return initialContext;
45         }
46
47         private void loadTemplate() {
48                 File templateFile = new File(filename);
49                 if (!templateFile.exists()) {
50                         throw new TemplateFileNotFoundException(filename);
51                 }
52                 if (templateWasLoaded() && !templateFileHasBeenModifiedAfterLoading(templateFile)) {
53                         return;
54                 }
55                 InputStream templateInputStream = null;
56                 Reader templateReader = null;
57                 try {
58                         templateInputStream = new FileInputStream(templateFile);
59                         templateReader = new InputStreamReader(templateInputStream, Charsets.UTF_8);
60                         Template template = TemplateParser.parse(templateReader);
61                         lastTemplate.set(new LastLoadedTemplate(template));
62                         template.getInitialContext().mergeContext(initialContext);
63                         for (Part part : parts) {
64                                 template.add(part);
65                         }
66                 } catch (FileNotFoundException e) {
67                         throw new TemplateFileNotFoundException(filename);
68                 } finally {
69                         Closer.close(templateReader);
70                         Closer.close(templateInputStream);
71                 }
72         }
73
74         private boolean templateWasLoaded() {
75                 return lastTemplate.get() != null;
76         }
77
78         private boolean templateFileHasBeenModifiedAfterLoading(File templateFile) {
79                 return templateFile.lastModified() > lastTemplate.get().getLoadTime();
80         }
81
82         @Override
83         public void add(Part part) {
84                 loadTemplate();
85                 parts.add(part);
86                 lastTemplate.get().getTemplate().add(part);
87         }
88
89         @Override
90         public void render(TemplateContext templateContext, Writer writer) throws TemplateException {
91                 loadTemplate();
92                 lastTemplate.get().getTemplate().render(templateContext, writer);
93         }
94
95         @Override
96         public Iterator<Part> iterator() {
97                 loadTemplate();
98                 return lastTemplate.get().getTemplate().iterator();
99         }
100
101         @Override
102         public int getLine() {
103                 loadTemplate();
104                 return lastTemplate.get().getTemplate().getLine();
105         }
106
107         @Override
108         public int getColumn() {
109                 loadTemplate();
110                 return lastTemplate.get().getTemplate().getColumn();
111         }
112
113         private static class LastLoadedTemplate {
114
115                 private final Template template;
116                 private final long loadTime = System.currentTimeMillis();
117
118                 private LastLoadedTemplate(Template template) {
119                         this.template = template;
120                 }
121
122                 public Template getTemplate() {
123                         return template;
124                 }
125
126                 public long getLoadTime() {
127                         return loadTime;
128                 }
129
130         }
131
132         /**
133          * Exception that signals that a template file could not be found.
134          *
135          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
136          */
137         public static class TemplateFileNotFoundException extends RuntimeException {
138
139                 public TemplateFileNotFoundException(String filename) {
140                         super(filename);
141                 }
142
143         }
144
145 }