summaryrefslogtreecommitdiffstats
path: root/config/src/main/java/com/yahoo/vespa/config/ConfigFileFormat.java
blob: 16a3ef3371d06d3e8e8d104e8b7825e203daa2b2 (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
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.config;

import com.yahoo.config.codegen.CNode;
import com.yahoo.config.codegen.InnerCNode;
import com.yahoo.config.codegen.LeafCNode;
import com.yahoo.slime.*;
import com.yahoo.text.Utf8;
import com.yahoo.vespa.config.util.ConfigUtils;

import java.io.*;
import java.util.Stack;

/**
 * @author Ulf Lilleengen
 */
public class ConfigFileFormat implements SlimeFormat, ObjectTraverser {

    private final InnerCNode root;
    private DataOutputStream out = null;
    private Stack<Node> nodeStack;

    public ConfigFileFormat(InnerCNode root) {
        this.root = root;
        this.nodeStack = new Stack<>();
    }

    private void printPrefix() throws IOException {
        for (Node node : nodeStack) {
            CNode cnode = node.node;
            if (cnode != root) {
                encodeString(cnode.getName());
                if (cnode.isArray) {
                    encodeString("[" + node.arrayIndex + "]");
                    if (!(cnode instanceof LeafCNode)) {
                        encodeString(".");
                    }
                } else if (cnode.isMap) {
                    encodeString("{\"" + node.mapKey + "\"}");
                    if (!(cnode instanceof LeafCNode)) {
                        encodeString(".");
                    }
                } else if (cnode instanceof LeafCNode) {
                    encodeString("");
                } else {
                    encodeString(".");
                }
            }
        }
        encodeString(" ");
    }

    private void encode(Inspector inspector, CNode node) throws IOException {
        switch (inspector.type()) {
            case BOOL:
                encodeValue(String.valueOf(inspector.asBool()), (LeafCNode) node);
                return;
            case LONG:
                encodeValue(String.valueOf(inspector.asLong()), (LeafCNode) node);
                return;
            case DOUBLE:
                encodeValue(String.valueOf(inspector.asDouble()), (LeafCNode) node);
                return;
            case STRING:
                encodeValue(inspector.asString(), (LeafCNode) node);
                return;
            case ARRAY:
                encodeArray(inspector, node);
                return;
            case OBJECT:
                if (node.isMap) {
                    encodeMap(inspector, node);
                } else {
                    encodeObject(inspector, node);
                }
                return;
            case NIX:
            case DATA:
                throw new IllegalArgumentException("Illegal config format supplied. Unknown type for field '" + node.getName() + "'");
        }
        throw new RuntimeException("Should not be reached");
    }

    private void encodeMap(Inspector inspector, final CNode node) {
        inspector.traverse(new ObjectTraverser() {
            @Override
            public void field(String name, Inspector inspector) {
                try {
                    nodeStack.push(new Node(node, -1, name));
                    if (inspector.type().equals(Type.OBJECT)) {
                        encodeObject(inspector, node);
                    } else {
                        encode(inspector, node);
                    }
                    nodeStack.pop();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

    private void encodeArray(Inspector inspector, final CNode node) {
        inspector.traverse(new ArrayTraverser() {
            @Override
            public void entry(int idx, Inspector inspector) {
                try {
                    nodeStack.push(new Node(node, idx, ""));
                    encode(inspector, node);
                    nodeStack.pop();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        });

    }

    private void encodeObject(Inspector inspector, CNode node) {
        if (!node.isArray && !node.isMap) {
            nodeStack.push(new Node(node));
            inspector.traverse(this);
            nodeStack.pop();
        } else {
            inspector.traverse(this);
        }
    }

    private void encodeValue(String value, LeafCNode node) throws IOException {
        printPrefix();
        try {
            if (node instanceof LeafCNode.StringLeaf) {
                encodeStringQuoted(value);
            } else if (node instanceof LeafCNode.IntegerLeaf) {
                //Integer.parseInt(value);
                encodeString(value);
            } else if (node instanceof LeafCNode.LongLeaf) {
                //Long.parseLong(value);
                encodeString(value);
            } else if (node instanceof LeafCNode.DoubleLeaf) {
                //Double.parseDouble(value);
                encodeString(value);
            } else if (node instanceof LeafCNode.BooleanLeaf) {
                encodeString(String.valueOf(Boolean.parseBoolean(value)));
            } else if (node instanceof LeafCNode.EnumLeaf) {
                // LeafCNode.EnumLeaf enumNode = (LeafCNode.EnumLeaf) node;
                // TODO: Reenable this when we can return illegal config id.
                // checkLegalEnumValue(enumNode, value);
                encodeString(value);
            } else {
                encodeStringQuoted(value);
            }
            encodeString("\n");
        } catch (Exception e) {
            throw new IllegalArgumentException("Unable to serialize field '" + node.getFullName() + "': ", e);
        }
    }

    private void checkLegalEnumValue(LeafCNode.EnumLeaf enumNode, String value) {
        boolean found = false;
        for (String legalVal : enumNode.getLegalValues()) {
            if (legalVal.equals(value)) {
                found = true;
            }
        }
        if (!found)
            throw new IllegalArgumentException("Illegal enum value '" + value + "'");
    }

    private void encodeStringQuoted(String s) throws IOException {
        encodeString("\"" + escapeString(s) + "\"");
    }

    private String escapeString(String s) {
        return ConfigUtils.escapeConfigFormatValue(s);
    }

    private void encodeString(String s) throws IOException {
        out.write(Utf8.toBytes(s));
    }

    @Override
    public void encode(OutputStream os, Slime slime) throws IOException {
        encode(os, slime.get());
    }

    private void encode(OutputStream os, Inspector inspector) throws IOException {
        this.out = new DataOutputStream(os);
        this.nodeStack = new Stack<>();
        nodeStack.push(new Node(root));
        encode(inspector, root);
    }

    @Override
    public void field(String fieldName,  Inspector inspector) {
        try {
            Node parent = nodeStack.peek();
            CNode child = parent.node.getChild(fieldName);
            if (child == null) {
                return; // Skip this field to optimistic
            }
            if (!child.isArray && !child.isMap && child instanceof LeafCNode) {
                nodeStack.push(new Node(child));
                encode(inspector, child);
                nodeStack.pop();
            } else {
                encode(inspector, child);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private class Node {
        final int arrayIndex;
        final String mapKey;
        final CNode node;
        Node(CNode node, int arrayIndex, String mapKey) {
            this.node = node;
            this.arrayIndex = arrayIndex;
            this.mapKey = mapKey;
        }

        Node(CNode node) {
            this(node, -1, "");
        }
    }
}