001package io.jstach.jstachio;
002
003import java.io.IOException;
004import java.io.OutputStream;
005
006import io.jstach.jstache.JStacheInterfaces;
007import io.jstach.jstachio.Output.EncodedOutput;
008import io.jstach.jstachio.Template.EncodedTemplate;
009
010/**
011 * A template and model combined with convenience methods.
012 * <p>
013 * This tuple like object is useful way to combine the correct template with a model if
014 * you have programmatic access to the template. This can be useful to a web framework
015 * perhaps as the return type of a web controller and because the template is already
016 * located the web framework does not have to re-find the template. An analog in the
017 * Spring framework would be <code>ModelAndView</code>.
018 * <p>
019 * It is purposely not parameterized for ease of use as dealing with generics via
020 * reflection can be difficult.
021 *
022 * @apiNote the interface is purposely sealed for now to see usage and feedback and one
023 * can make their own similar version with {@link JStacheInterfaces}.
024 * @author agentgt
025 *
026 */
027public sealed interface TemplateModel {
028
029        /**
030         * Template.
031         * @return template to use for execution
032         */
033        Template<?> template();
034
035        /**
036         * Model.
037         * @return model to be executed on
038         */
039        Object model();
040
041        /**
042         * Renders the passed in model directly to an appendable like output.
043         * @param <A> output type
044         * @param <E> error type
045         * @param output to write to.
046         * @return the passed in output
047         * @throws E if an error occurs while writing to output
048         */
049        public <A extends io.jstach.jstachio.Output<E>, E extends Exception> A execute(A output) throws E;
050
051        /**
052         * Renders the passed in model directly to a binary stream possibly leveraging
053         * pre-encoded parts of the template.
054         * @param <A> output type
055         * @param <E> error type
056         * @param output to write to.
057         * @return the passed in output
058         * @throws E if an error occurs while writing to output
059         */
060        default <A extends io.jstach.jstachio.Output.EncodedOutput<E>, E extends Exception> A write(A output) throws E {
061                return execute(output);
062        }
063
064        /**
065         * Convenience method to write directly to an outputstream with the templates
066         * character encoding.
067         * @param outputStream outputStream to write to
068         * @throws IOException if an error happens while writting to the stream.
069         */
070        default void write(OutputStream outputStream) throws IOException {
071                write(Output.of(outputStream, template().templateCharset()));
072        }
073
074        /**
075         * Renders the template to a String.
076         * @return the executed template as a string.
077         */
078        default String execute() {
079                return execute(Output.of(new StringBuilder())).getBuffer().toString();
080        }
081
082        /**
083         * Creates a template model pair.
084         * @param <T> model type
085         * @param template encoded template
086         * @param model model instance
087         * @return the template executable designed for encoded templates.
088         */
089        public static <T> TemplateModel of(EncodedTemplate<T> template, T model) {
090                return new EncodedTemplateExecutable<>(template, model);
091        }
092
093        /**
094         * Creates a template model pair.
095         * @param <T> model type
096         * @param template encoded template
097         * @param model model instance
098         * @return the template executable.
099         */
100        public static <T> TemplateModel of(Template<T> template, T model) {
101                if (template instanceof EncodedTemplate<T> et) {
102                        return of(et, model);
103                }
104                return new DefaultTemplateExecutable<>(template, model);
105        }
106
107}
108
109record DefaultTemplateExecutable<T> (Template<T> delegateTemplate, T _model) implements TemplateModel {
110
111        static final String ERROR_MESSAGE = "The model passed into this TemplateModel is not correct";
112
113        @Override
114        public <A extends io.jstach.jstachio.Output<E>, E extends Exception> A execute(A output) throws E {
115                return delegateTemplate.execute(this._model, output);
116        }
117
118        @Override
119        public Template<?> template() {
120                return delegateTemplate;
121        }
122
123        @Override
124        public Object model() {
125                var m = _model;
126                // Both eclipse and checkerframework seem to be unable to apply nonnull
127                // to records with parameters
128                if (m == null) {
129                        throw new NullPointerException("model is null");
130                }
131                return m;
132        }
133
134}
135
136record EncodedTemplateExecutable<T> (EncodedTemplate<T> delegateTemplate, T _model) implements TemplateModel {
137
138        @Override
139        public <A extends io.jstach.jstachio.Output<E>, E extends Exception> A execute(A output) throws E {
140                return delegateTemplate.execute(this._model, output);
141        }
142
143        @Override
144        public <A extends EncodedOutput<E>, E extends Exception> A write(A output) throws E {
145                return delegateTemplate.write(_model, output);
146        }
147
148        @Override
149        public Template<?> template() {
150                return delegateTemplate;
151        }
152
153        @Override
154        public Object model() {
155                var m = _model;
156                // Both eclipse and checkerframework seem to be unable to apply nonnull
157                // to records with parameters
158                if (m == null) {
159                        throw new NullPointerException("model is null");
160                }
161                return m;
162        }
163
164}