001package io.jstach.jstachio.spi;
002
003import static io.jstach.jstachio.spi.Templates.sneakyThrow;
004import static io.jstach.jstachio.spi.Templates.validateEncoding;
005
006import io.jstach.jstachio.JStachio;
007import io.jstach.jstachio.Output;
008import io.jstach.jstachio.Output.EncodedOutput;
009import io.jstach.jstachio.Template;
010import io.jstach.jstachio.TemplateInfo;
011import io.jstach.jstachio.TemplateModel;
012import io.jstach.jstachio.context.ContextJStachio;
013import io.jstach.jstachio.context.ContextNode;
014import io.jstach.jstachio.context.ContextTemplate;
015import io.jstach.jstachio.spi.JStachioFilter.FilterChain;
016
017/**
018 * An abstract jstachio that just needs a {@link JStachioExtensions} container.
019 * <p>
020 * To extend just override {@link #extensions()}.
021 *
022 * @see JStachioExtensions
023 * @author agentgt
024 */
025public abstract class AbstractJStachio implements JStachio, JStachioExtensions.Provider, ContextJStachio {
026
027        /**
028         * Do nothing constructor
029         */
030        public AbstractJStachio() {
031
032        }
033
034        @Override
035        public <A extends Output<E>, E extends Exception> A execute(Object model, A appendable) throws E {
036                var t = _findTemplate(model);
037                return t.execute(_resolveModel(model), appendable);
038        }
039
040        @Override
041        public <A extends EncodedOutput<E>, E extends Exception> A write(Object model, A appendable) throws E {
042                var t = _findTemplate(model);
043                validateEncoding(t, appendable);
044                return t.write(_resolveModel(model), appendable);
045        }
046
047        /*
048         * IF YOU want this method to be not final please file a bug.
049         */
050        @Override
051        public final <A extends EncodedOutput<E>, E extends Exception> A write(Object model, ContextNode context, A output)
052                        throws E {
053                var t = _findTemplate(model);
054                validateEncoding(t, output);
055                var ct = ContextTemplate.of(t);
056                return ct.write(_resolveModel(model), context, output);
057        }
058
059        /*
060         * IF YOU want this method to be not final please file a bug.
061         */
062        @Override
063        public final <A extends Output<E>, E extends Exception> A execute(Object model, ContextNode context, A appendable)
064                        throws E {
065                var t = _findTemplate(model);
066                var ct = ContextTemplate.of(t);
067                return ct.execute(_resolveModel(model), context, appendable);
068        }
069
070        /*
071         * IF YOU want this method to be not final please file a bug.
072         */
073        @Override
074        public final Template<Object> findTemplate(Object model) {
075                return _findTemplate(model);
076        }
077
078        /*
079         * IF YOU want this method protected (thus overrideable) please file a bug.
080         */
081        @SuppressWarnings({ "unchecked" })
082        private Template<Object> _findTemplate(Object model) {
083                TemplateInfo template;
084                if (model instanceof TemplateModel tm) {
085                        /*
086                         * TemplateModel has the template and model combined
087                         */
088                        template = tm.template();
089                        model = tm.model();
090                }
091                else {
092                        template = template(model.getClass());
093                }
094                var filter = loadFilter(model, template);
095                Template<Object> t = (Template<Object>) FilterChain.toTemplate(filter, template);
096                return t;
097        }
098
099        private static Object _resolveModel(Object model) {
100                if (model instanceof TemplateModel tm) {
101                        return tm.model();
102                }
103                return model;
104        }
105
106        /**
107         * Loads the filter and checks if it can process the model and template.
108         * @param model to render
109         * @param template loaded by {@link #template(Class)}
110         * @return filter chain that can process model
111         */
112        protected final FilterChain loadFilter(Object model, TemplateInfo template) {
113                var jstachioFilter = extensions().getFilter();
114                var filter = FilterChain.of(jstachioFilter, template);
115                if (filter.isBroken(model)) {
116                        boolean isReflectiveTemplate = Templates.isReflectionTemplate(template);
117                        final String ind = "\n\t";
118                        String reason = "";
119                        if (isReflectiveTemplate) {
120                                reason = " This is usually because the template "
121                                                + "has not been compiled and reflection based rendering is not available.";
122                        }
123                        throw new BrokenFilterException( //
124                                        "Filter chain unable to process template/model." + reason //
125                                                        + ind + "template: \"" + template.description() + "\"" //
126                                                        + ind + "model type: \"" + model.getClass() + "\"" //
127                                                        + ind + "reflection used: \"" + isReflectiveTemplate + "\"");
128                }
129                return filter;
130        }
131
132        /*
133         * IF YOU want this method to be not final please file a bug.
134         */
135        @Override
136        public final boolean supportsType(Class<?> modelType) {
137                if (TemplateModel.class.isAssignableFrom(modelType)) {
138                        return true;
139                }
140                if (Templates.isIgnoredType(modelType)) {
141                        return false;
142                }
143                return extensions().getTemplateFinder().supportsType(modelType);
144        }
145
146        /**
147         * Finds the template by model class wrapping any exceptions.
148         * @param modelType the class of the model.
149         * @return found template never <code>null</code>.
150         */
151        protected TemplateInfo template(Class<?> modelType) {
152                try {
153                        return extensions().getTemplateFinder().findTemplate(modelType);
154                }
155                catch (Exception e) {
156                        sneakyThrow(e);
157                        throw new RuntimeException(e);
158                }
159        }
160
161}
162
163class DefaultJStachio extends AbstractJStachio {
164
165        private final JStachioExtensions extensions;
166
167        public DefaultJStachio(JStachioExtensions extensions) {
168                super();
169                this.extensions = extensions;
170        }
171
172        @Override
173        public JStachioExtensions extensions() {
174                return this.extensions;
175        }
176
177}