aboutsummaryrefslogtreecommitdiffstats
path: root/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Template.java
blob: 3931202c24f0e5d7e7ca1226067a6690632c7d5b (plain) (blame)
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
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.task.util.template;

import com.yahoo.vespa.hosted.node.admin.task.util.text.Cursor;
import com.yahoo.vespa.hosted.node.admin.task.util.text.CursorRange;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.function.Consumer;

/**
 * The Java representation of a template text.
 *
 * <p>A template is a sequence of literal text and dynamic sections defined by %{...} directives:</p>
 *
 * <pre>
 *     template: section*
 *     section: literal | variable | list
 *     literal: # plain text not containing %{
 *     variable: %{=identifier}
 *     list: %{list identifier}template%{end}
 *     identifier: # a valid Java identifier
 * </pre>
 *
 * <p>Any newline (\n) following a non-variable directive is removed.</p>
 *
 * <p><b>Instantiate</b> the template to get a form ({@link #instantiate()}), fill it
 * (e.g. {@link Form#set(String, String) Form.set()}), and render the String ({@link Form#render()}).</p>
 *
 * @see Form
 * @see TemplateParser
 * @see TemplateFile
 * @author hakonhall
 */
public class Template {
    private final Cursor start;
    private final Cursor end;

    private final List<Consumer<FormBuilder>> sections = new ArrayList<>();
    private final HashSet<String> names = new HashSet<>();

    Template(Cursor start) {
        this.start = new Cursor(start);
        this.end = new Cursor(start);
    }

    public Form instantiate() {
        return FormBuilder.build(range(), sections);
    }

    void appendLiteralSection(Cursor end) {
        CursorRange range = verifyAndUpdateEnd(end);
        sections.add((FormBuilder builder) -> builder.addLiteralSection(range));
    }

    void appendVariableSection(String name, Cursor nameOffset, Cursor end) {
        CursorRange range = verifyAndUpdateEnd(end);
        verifyAndAddNewName(name, nameOffset);
        sections.add(formBuilder -> formBuilder.addVariableSection(range, name, nameOffset));
    }

    void appendListSection(String name, Cursor nameCursor, Cursor end, Template body) {
        CursorRange range = verifyAndUpdateEnd(end);
        verifyAndAddNewName(name, nameCursor);
        sections.add(formBuilder -> formBuilder.addListSection(range, name, body));
    }

    private CursorRange range() { return new CursorRange(start, end); }

    private CursorRange verifyAndUpdateEnd(Cursor newEnd) {
        var range = new CursorRange(this.end, newEnd);
        this.end.set(newEnd);
        return range;
    }

    private String verifyAndAddNewName(String name, Cursor nameOffset) {
        if (!names.add(name)) {
            throw new IllegalArgumentException("'" + name + "' at " +
                                               nameOffset.calculateLocation().lineAndColumnText() +
                                               " has already been defined");
        }
        return name;
    }
}