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