2 * Copyright (c) 2018 Pantheon Technologies, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.yang.model.export;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12 import static java.util.Objects.requireNonNull;
13 import static org.eclipse.jdt.annotation.DefaultLocation.PARAMETER;
14 import static org.eclipse.jdt.annotation.DefaultLocation.RETURN_TYPE;
16 import com.google.common.base.CharMatcher;
17 import com.google.common.base.Splitter;
18 import com.google.common.base.Strings;
19 import com.google.common.collect.AbstractIterator;
20 import com.google.common.collect.ImmutableMap;
21 import com.google.common.collect.ImmutableSet;
22 import java.util.ArrayDeque;
23 import java.util.Collection;
24 import java.util.Deque;
25 import java.util.Iterator;
27 import java.util.Queue;
29 import org.eclipse.jdt.annotation.NonNull;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.opendaylight.yangtools.yang.common.QName;
33 import org.opendaylight.yangtools.yang.common.QNameModule;
34 import org.opendaylight.yangtools.yang.common.YangConstants;
35 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
36 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
37 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
40 * This is an iterator over strings needed to assemble a YANG snippet.
42 * @author Robert Varga
44 @NonNullByDefault({ PARAMETER, RETURN_TYPE })
45 final class YangTextSnippetIterator extends AbstractIterator<@NonNull String> {
46 // https://tools.ietf.org/html/rfc7950#section-6.1.3
47 // An unquoted string is any sequence of characters that does not
48 // contain any space, tab, carriage return, or line feed characters, a
49 // single or double quote character, a semicolon (";"), braces ("{" or
50 // "}"), or comment sequences ("//", "/*", or "*/").
51 // Newline is treated separately, so it is not included here
52 private static final CharMatcher NEED_QUOTE_MATCHER = CharMatcher.anyOf(" \t\r'\";{}");
53 private static final CharMatcher DQUOT_MATCHER = CharMatcher.is('"');
54 private static final Splitter NEWLINE_SPLITTER = Splitter.on('\n');
55 private static final Collection<StatementDefinition> QUOTE_MULTILINE_STATEMENTS = ImmutableSet.of(
56 YangStmtMapping.CONTACT,
57 YangStmtMapping.DESCRIPTION,
58 YangStmtMapping.ERROR_MESSAGE,
59 YangStmtMapping.ORGANIZATION,
60 YangStmtMapping.REFERENCE);
63 * https://tools.ietf.org/html/rfc6087#section-4.3:
64 * In general, it is suggested that substatements containing very common
65 * default values SHOULD NOT be present. The following substatements
66 * are commonly used with the default value, which would make the module
67 * difficult to read if used everywhere they are allowed.
69 private static final Map<StatementDefinition, String> DEFAULT_STATEMENTS =
70 ImmutableMap.<StatementDefinition, String>builder()
71 .put(YangStmtMapping.CONFIG, "true")
72 .put(YangStmtMapping.MANDATORY, "true")
73 .put(YangStmtMapping.MAX_ELEMENTS, "unbounded")
74 .put(YangStmtMapping.MIN_ELEMENTS, "0")
75 .put(YangStmtMapping.ORDERED_BY, "system")
76 .put(YangStmtMapping.REQUIRE_INSTANCE, "true")
77 .put(YangStmtMapping.STATUS, "current")
78 .put(YangStmtMapping.YIN_ELEMENT, "false")
81 private static final String INDENT = " ";
82 private static final int INDENT_STRINGS_SIZE = 16;
83 private static final String[] INDENT_STRINGS = new String[INDENT_STRINGS_SIZE];
86 for (int i = 0; i < INDENT_STRINGS_SIZE; i++) {
87 INDENT_STRINGS[i] = Strings.repeat(INDENT, i).intern();
91 private enum Quoting {
93 * No quoting necessary.
97 * Argument is empty, quote an empty string.
101 * Quote on the same line.
105 * Quote starting on next line.
111 * We normally have up to 10 strings:
123 * But all of this is typically not used:
124 * - statements usually do not have a prefix, saving two items
125 * - arguments are not typically quoted, saving another two
127 * In case we get into a multi-line argument, we are already splitting strings, so the cost of growing
128 * the queue is negligible
130 private final Queue<String> strings = new ArrayDeque<>(8);
131 // Let's be modest, 16-level deep constructs are not exactly common.
132 private final Deque<Iterator<? extends DeclaredStatement<?>>> stack = new ArrayDeque<>(8);
133 private final Map<QNameModule, @NonNull String> namespaces;
134 private final Set<StatementDefinition> ignoredStatements;
135 private final boolean omitDefaultStatements;
137 YangTextSnippetIterator(final DeclaredStatement<?> stmt, final Map<QNameModule, @NonNull String> namespaces,
138 final Set<StatementDefinition> ignoredStatements, final boolean omitDefaultStatements) {
139 this.namespaces = requireNonNull(namespaces);
140 this.ignoredStatements = requireNonNull(ignoredStatements);
141 this.omitDefaultStatements = omitDefaultStatements;
142 pushStatement(requireNonNull(stmt));
146 protected @NonNull String computeNext() {
147 // We may have some strings stashed, take one out, if that is the case
148 final String nextString = strings.poll();
149 if (nextString != null) {
153 final Iterator<? extends DeclaredStatement<?>> it = stack.peek();
156 // Post-end of data, the user will never see this
160 // Try to push next child
161 while (it.hasNext()) {
162 if (pushStatement(it.next())) {
163 return strings.remove();
167 // End of children, close the parent statement
171 return strings.remove();
175 * Push a statement to the stack. A successfully-pushed statement results in strings not being empty.
177 * @param stmt Statement to push into strings
178 * @return True if the statement was pushed. False if the statement was suppressed.
180 private boolean pushStatement(final DeclaredStatement<?> stmt) {
181 final StatementDefinition def = stmt.statementDefinition();
182 if (ignoredStatements.contains(def)) {
186 final Collection<? extends DeclaredStatement<?>> children = stmt.declaredSubstatements();
187 if (omitDefaultStatements && children.isEmpty()) {
188 // This statement does not have substatements, check if its value matches the declared default, like
189 // "config true", "mandatory false", etc.
190 final String suppressValue = DEFAULT_STATEMENTS.get(def);
191 if (suppressValue != null && suppressValue.equals(stmt.rawArgument())) {
196 // New statement: push indent
199 // Add statement prefixed with namespace if needed
200 final QName stmtName = def.getStatementName();
201 addNamespace(stmtName.getModule());
202 strings.add(stmtName.getLocalName());
204 // Add argument, quoted and properly indented if need be
205 addArgument(def, stmt.rawArgument());
207 if (!children.isEmpty()) {
208 // Open the statement and push child iterator
210 stack.push(children.iterator());
212 // Close the statement
219 private void addIndent() {
220 int depth = stack.size();
221 while (depth >= INDENT_STRINGS_SIZE) {
222 strings.add(INDENT_STRINGS[INDENT_STRINGS_SIZE - 1]);
223 depth -= INDENT_STRINGS_SIZE;
226 strings.add(INDENT_STRINGS[depth]);
230 private void addNamespace(final QNameModule namespace) {
231 if (YangConstants.RFC6020_YIN_MODULE.equals(namespace)) {
232 // Default namespace, no prefix needed
236 final @Nullable String prefix = namespaces.get(namespace);
237 checkArgument(prefix != null, "Failed to find prefix for namespace %s", namespace);
238 verify(!prefix.isEmpty(), "Empty prefix for namespace %s", namespace);
243 private void addArgument(final StatementDefinition def, final @Nullable String arg) {
245 // No argument, nothing to do
249 switch (quoteKind(def, arg)) {
251 strings.add(" \"\"");
259 strings.add(DQUOT_MATCHER.replaceFrom(arg, "\\\""));
265 strings.add(INDENT + '\"');
267 final Iterator<String> it = NEWLINE_SPLITTER.split(DQUOT_MATCHER.replaceFrom(arg, "\\\"")).iterator();
268 final String first = it.next();
269 if (!first.isEmpty()) {
273 while (it.hasNext()) {
275 final String str = it.next();
276 if (!str.isEmpty()) {
278 strings.add(INDENT + ' ');
285 throw new IllegalStateException("Illegal quoting for " + def + " argument \"" + arg + "\"");
289 private static Quoting quoteKind(final StatementDefinition def, final String str) {
291 return Quoting.EMPTY;
293 if (QUOTE_MULTILINE_STATEMENTS.contains(def) || str.indexOf('\n') != -1) {
294 return Quoting.MULTILINE;
296 if (NEED_QUOTE_MATCHER.matchesAnyOf(str) || str.contains("//") || str.contains("/*") || str.contains("*/")) {
297 return Quoting.SIMPLE;