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 java.util.Objects.requireNonNull;
11 import static org.eclipse.jdt.annotation.DefaultLocation.PARAMETER;
12 import static org.eclipse.jdt.annotation.DefaultLocation.RETURN_TYPE;
14 import com.google.common.base.CharMatcher;
15 import com.google.common.base.Splitter;
16 import com.google.common.collect.AbstractIterator;
17 import com.google.common.collect.ImmutableMap;
18 import com.google.common.collect.ImmutableSet;
19 import java.util.ArrayDeque;
20 import java.util.Collection;
21 import java.util.Deque;
22 import java.util.Iterator;
23 import java.util.Optional;
24 import java.util.Queue;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
30 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
31 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
34 * This is an iterator over strings needed to assemble a YANG snippet.
36 * @author Robert Varga
38 @NonNullByDefault({ PARAMETER, RETURN_TYPE })
39 final class YangTextSnippetIterator extends AbstractIterator<@NonNull String> {
40 // https://tools.ietf.org/html/rfc7950#section-6.1.3
41 // An unquoted string is any sequence of characters that does not
42 // contain any space, tab, carriage return, or line feed characters, a
43 // single or double quote character, a semicolon (";"), braces ("{" or
44 // "}"), or comment sequences ("//", "/*", or "*/").
45 // Newline is treated separately, so it is not included here
46 private static final CharMatcher NEED_QUOTE_MATCHER = CharMatcher.anyOf(" \t\r'\";{}");
47 private static final CharMatcher DQUOT_MATCHER = CharMatcher.is('"');
48 private static final Splitter NEWLINE_SPLITTER = Splitter.on('\n');
49 private static final ImmutableSet<StatementDefinition> QUOTE_MULTILINE_STATEMENTS = ImmutableSet.of(
50 YangStmtMapping.CONTACT,
51 YangStmtMapping.DESCRIPTION,
52 YangStmtMapping.ERROR_MESSAGE,
53 YangStmtMapping.ORGANIZATION,
54 YangStmtMapping.REFERENCE);
57 * https://tools.ietf.org/html/rfc6087#section-4.3:
58 * In general, it is suggested that substatements containing very common
59 * default values SHOULD NOT be present. The following substatements
60 * are commonly used with the default value, which would make the module
61 * difficult to read if used everywhere they are allowed.
63 private static final ImmutableMap<StatementDefinition, String> DEFAULT_STATEMENTS =
64 ImmutableMap.<StatementDefinition, String>builder()
65 .put(YangStmtMapping.CONFIG, "true")
66 .put(YangStmtMapping.MANDATORY, "true")
67 .put(YangStmtMapping.MAX_ELEMENTS, "unbounded")
68 .put(YangStmtMapping.MIN_ELEMENTS, "0")
69 .put(YangStmtMapping.ORDERED_BY, "system")
70 .put(YangStmtMapping.REQUIRE_INSTANCE, "true")
71 .put(YangStmtMapping.STATUS, "current")
72 .put(YangStmtMapping.YIN_ELEMENT, "false")
75 private static final String INDENT = " ";
76 private static final int INDENT_STRINGS_SIZE = 16;
77 private static final String[] INDENT_STRINGS = new String[INDENT_STRINGS_SIZE];
80 for (int i = 0; i < INDENT_STRINGS_SIZE; i++) {
81 INDENT_STRINGS[i] = INDENT.repeat(i).intern();
85 private enum Quoting {
87 * No quoting necessary.
91 * Argument is empty, quote an empty string.
95 * Quote on the same line.
99 * Quote starting on next line.
105 * We normally have up to 10 strings:
117 * But all of this is typically not used:
118 * - statements usually do not have a prefix, saving two items
119 * - arguments are not typically quoted, saving another two
121 * In case we get into a multi-line argument, we are already splitting strings, so the cost of growing
122 * the queue is negligible
124 private final Queue<String> strings = new ArrayDeque<>(8);
125 // Let's be modest, 16-level deep constructs are not exactly common.
126 private final Deque<Iterator<? extends DeclaredStatement<?>>> stack = new ArrayDeque<>(8);
127 private final Set<StatementDefinition> ignoredStatements;
128 private final StatementPrefixResolver resolver;
129 private final boolean omitDefaultStatements;
131 YangTextSnippetIterator(final DeclaredStatement<?> stmt, final StatementPrefixResolver resolver,
132 final Set<StatementDefinition> ignoredStatements, final boolean omitDefaultStatements) {
133 this.resolver = requireNonNull(resolver);
134 this.ignoredStatements = requireNonNull(ignoredStatements);
135 this.omitDefaultStatements = omitDefaultStatements;
136 pushStatement(requireNonNull(stmt));
140 protected @NonNull String computeNext() {
141 // We may have some strings stashed, take one out, if that is the case
142 final String nextString = strings.poll();
143 if (nextString != null) {
147 final Iterator<? extends DeclaredStatement<?>> it = stack.peek();
150 // Post-end of data, the user will never see this
154 // Try to push next child
155 while (it.hasNext()) {
156 if (pushStatement(it.next())) {
157 return strings.remove();
161 // End of children, close the parent statement
165 return strings.remove();
169 * Push a statement to the stack. A successfully-pushed statement results in strings not being empty.
171 * @param stmt Statement to push into strings
172 * @return True if the statement was pushed. False if the statement was suppressed.
174 private boolean pushStatement(final DeclaredStatement<?> stmt) {
175 final StatementDefinition def = stmt.statementDefinition();
176 if (ignoredStatements.contains(def)) {
180 final Collection<? extends DeclaredStatement<?>> children = stmt.declaredSubstatements();
181 if (omitDefaultStatements && children.isEmpty()) {
182 // This statement does not have substatements, check if its value matches the declared default, like
183 // "config true", "mandatory false", etc.
184 final String suppressValue = DEFAULT_STATEMENTS.get(def);
185 if (suppressValue != null && suppressValue.equals(stmt.rawArgument())) {
190 // New statement: push indent
193 // Add statement prefixed with namespace if needed
194 final Optional<String> prefix = resolver.findPrefix(stmt);
195 if (prefix.isPresent()) {
196 strings.add(prefix.get());
199 strings.add(def.getStatementName().getLocalName());
201 // Add argument, quoted and properly indented if need be
202 addArgument(def, stmt.rawArgument());
204 if (!children.isEmpty()) {
205 // Open the statement and push child iterator
207 stack.push(children.iterator());
209 // Close the statement
216 private void addIndent() {
217 int depth = stack.size();
218 while (depth >= INDENT_STRINGS_SIZE) {
219 strings.add(INDENT_STRINGS[INDENT_STRINGS_SIZE - 1]);
220 depth -= INDENT_STRINGS_SIZE;
223 strings.add(INDENT_STRINGS[depth]);
227 private void addArgument(final StatementDefinition def, final @Nullable String arg) {
229 // No argument, nothing to do
233 switch (quoteKind(def, arg)) {
235 strings.add(" \"\"");
243 strings.add(DQUOT_MATCHER.replaceFrom(arg, "\\\""));
249 strings.add(INDENT + '\"');
251 final Iterator<String> it = NEWLINE_SPLITTER.split(DQUOT_MATCHER.replaceFrom(arg, "\\\"")).iterator();
252 final String first = it.next();
253 if (!first.isEmpty()) {
257 while (it.hasNext()) {
259 final String str = it.next();
260 if (!str.isEmpty()) {
262 strings.add(INDENT + ' ');
269 throw new IllegalStateException("Illegal quoting for " + def + " argument \"" + arg + "\"");
273 private static Quoting quoteKind(final StatementDefinition def, final String str) {
275 return Quoting.EMPTY;
277 if (QUOTE_MULTILINE_STATEMENTS.contains(def) || str.indexOf('\n') != -1) {
278 return Quoting.MULTILINE;
280 if (NEED_QUOTE_MATCHER.matchesAnyOf(str) || str.contains("//") || str.contains("/*") || str.contains("*/")) {
281 return Quoting.SIMPLE;