summaryrefslogtreecommitdiff
path: root/support/texplate/source/main/java/org/islandoftex/texplate/util/MergingUtils.java
blob: d7ffbddc4da0ae66ae0aaacbd2d08abc23fafcbe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// SPDX-License-Identifier: BSD-3-Clause
package org.islandoftex.texplate.util;

import io.vavr.control.Try;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.TemplateInitException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.RuntimeSingleton;
import org.apache.velocity.runtime.parser.ParseException;
import org.islandoftex.texplate.exceptions.TemplateMergingException;
import org.islandoftex.texplate.model.Template;
import org.islandoftex.texplate.model.handlers.Handler;
import org.slf4j.helpers.NOPLoggerFactory;

import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.Writer;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

/**
 * Merging utilities.
 *
 * @version 1.0
 * @since 1.0
 */
public class MergingUtils {

    /**
     * Merges both template and data.
     *
     * @param template The template object.
     * @param map      The data map.
     * @param output   The output path.
     * @param cmap     The configuration map.
     * @return The length of the generated output.
     * @throws TemplateMergingException The merging failed.
     */
    private static long mergeTemplate(Template template,
                                      Map<String, String> map, Path output,
                                      Map<String, Object> cmap)
            throws TemplateMergingException {

        // create the context map
        Map<String, Object> context = handle(template, map, cmap);

        // create a file writer for
        // the output reference
        try (Writer writer = new FileWriter(output.toFile())) {

            // the document is actually read
            // into a string reader
            StringReader reader = new StringReader(template.getDocument());

            // load both runtime services and
            // the template model from Velocity
            RuntimeServices services = RuntimeSingleton.getRuntimeServices();
            services.addProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE,
                    new NOPLoggerFactory().getLogger(""));
            org.apache.velocity.Template reference
                    = new org.apache.velocity.Template();

            // set both runtime services
            // and document data into the
            // template document
            reference.setRuntimeServices(services);
            reference.setData(services.parse(reader, reference));
            reference.initDocument();

            // create the context based on
            // the data map previously set
            VelocityContext entries = new VelocityContext(context);

            // merge both template and data
            // into the file writer
            reference.merge(entries, writer);

        } catch (IOException | MethodInvocationException
                | ParseErrorException | ParseException
                | ResourceNotFoundException
                | TemplateInitException exception) {

            // an exception has happened, so a
            // new exception is thrown, attaching
            // the original throwable cause 
            throw new TemplateMergingException("An error occurred while "
                    + "trying to merge the template reference with the "
                    + "provided data. Make sure the template is correct "
                    + "and try again. The raised exception might give us "
                    + "some hints on what exactly happened. Typically, "
                    + "make sure the template strictly follows the "
                    + "Velocity 2.0 language syntax.", exception);
        }

        // simply return the length of
        // the generated output file
        return output.toFile().length();
    }

    /**
     * Merges template and data.
     *
     * @param template The template model.
     * @param map      The data map.
     * @param output   The path reference.
     * @param cmap     The configuration map.
     * @return The length of the generated output, enclosed as a Try object.
     */
    public static Try<Long> merge(Template template, Map<String, String> map,
                                  Path output, Map<String, Object> cmap) {
        return Try.of(() -> mergeTemplate(template, map, output, cmap));
    }

    /**
     * Handles the context map.
     *
     * @param template The template model.
     * @param map      The context map.
     * @return The new context map.
     */
    private static Map<String, Object> handle(Template template,
                                              Map<String, String> map,
                                              Map<String, Object> cmap) {

        // no handlers found
        if (template.getHandlers() == null) {

            // create a new map from the
            // command line map and put the
            // values from the configuration
            // file, if absent
            Map<String, Object> result = new HashMap<>(map);
            cmap.forEach(result::putIfAbsent);
            return result;

        } else {

            // get default handlers and
            // set the resulting map
            Map<String, Handler> handlers = HandlerUtils.getHandlers();
            Map<String, Object> result = new HashMap<>();

            // check each key from the map
            map.forEach((String key, String value) -> {

                // there is a handler for
                // the current key
                if (template.getHandlers().containsKey(key)) {

                    // the handler seems valid
                    if (handlers.containsKey(template.getHandlers().get(key))) {

                        // apply the handler and
                        // store the value in the map
                        result.put(key, handlers.get(template.
                                getHandlers().get(key)).apply(value));
                    } else {

                        // simply store the value
                        result.put(key, value);
                        // TODO: should we warn about an invalid handler?
                    }
                } else {

                    // simply store the value
                    result.put(key, value);
                }
            });

            // put remaining values from
            // the configuration file, if
            // absent
            cmap.forEach(result::putIfAbsent);

            // return the map
            return result;
        }
    }

}