2 * Copyright (c) 2013 Cisco Systems, Inc. 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.controller.yang.model.parser.impl;
11 import java.net.URISyntaxException;
12 import java.text.DateFormat;
13 import java.text.ParseException;
14 import java.util.Date;
15 import java.util.HashSet;
17 import java.util.regex.Pattern;
19 import org.antlr.v4.runtime.tree.ParseTree;
20 import org.opendaylight.controller.antlrv4.code.gen.YangParser;
21 import org.opendaylight.controller.antlrv4.code.gen.YangParser.Belongs_to_stmtContext;
22 import org.opendaylight.controller.antlrv4.code.gen.YangParser.Import_stmtContext;
23 import org.opendaylight.controller.antlrv4.code.gen.YangParser.Include_stmtContext;
24 import org.opendaylight.controller.antlrv4.code.gen.YangParser.Module_header_stmtsContext;
25 import org.opendaylight.controller.antlrv4.code.gen.YangParser.Namespace_stmtContext;
26 import org.opendaylight.controller.antlrv4.code.gen.YangParser.Prefix_stmtContext;
27 import org.opendaylight.controller.antlrv4.code.gen.YangParser.Revision_date_stmtContext;
28 import org.opendaylight.controller.antlrv4.code.gen.YangParser.Revision_stmtContext;
29 import org.opendaylight.controller.antlrv4.code.gen.YangParser.Revision_stmtsContext;
30 import org.opendaylight.controller.antlrv4.code.gen.YangParser.Submodule_header_stmtsContext;
31 import org.opendaylight.controller.antlrv4.code.gen.YangParser.Submodule_stmtContext;
32 import org.opendaylight.controller.antlrv4.code.gen.YangParser.Yang_version_stmtContext;
33 import org.opendaylight.controller.antlrv4.code.gen.YangParserBaseListener;
34 import org.opendaylight.controller.yang.model.parser.util.YangModelBuilderUtil;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
39 * Validation listener that validates yang statements according to RFC-6020.
40 * This validator expects only one module or submodule per file.
44 * TODO is this assumption(module per file) correct ? if so, should a check be
47 * TODO break into smaller classes e.g. class for header statements, body
50 final class YangModelValidationListener extends YangParserBaseListener {
52 private static final Logger logger = LoggerFactory
53 .getLogger(YangModelValidationListener.class);
55 private final Set<String> uniquePrefixes;
56 private final Set<String> uniqueImports;
57 private final Set<String> uniqueIncludes;
59 public YangModelValidationListener() {
61 uniquePrefixes = new HashSet<String>();
62 uniqueImports = new HashSet<String>();
63 uniqueIncludes = new HashSet<String>();
69 * <li>Identifier contains only permitted characters</li>
70 * <li>One revision statements present</li>
71 * <li>One header statements present</li>
75 public void enterModule_stmt(YangParser.Module_stmtContext ctx) {
76 String moduleName = getName(ctx);
78 checkIdentifier(moduleName, "Module");
80 checkPresentChildOfType(ctx, Revision_stmtsContext.class,
81 f("Missing revision statements in module:%s", moduleName), true);
83 checkPresentChildOfType(ctx, Module_header_stmtsContext.class,
84 f("Missing header statements in module:%s", moduleName), true);
90 * <li>Identifier contains only permitted characters</li>
91 * <li>One revision statements present</li>
92 * <li>One header statements present</li>
96 public void enterSubmodule_stmt(Submodule_stmtContext ctx) {
97 String submoduleName = getName(ctx);
99 checkIdentifier(submoduleName, "Submodule");
101 checkPresentChildOfType(
103 Revision_stmtsContext.class,
104 f("Missing revision statements in submodule:%s", submoduleName),
107 checkPresentChildOfType(ctx, Submodule_header_stmtsContext.class,
108 f("Missing header statements in submodule:%s", submoduleName),
115 * <li>One Belongs-to statement present</li>
119 public void enterSubmodule_header_stmts(Submodule_header_stmtsContext ctx) {
120 String submoduleName = getRootParentName(ctx);
122 checkPresentChildOfType(
124 Belongs_to_stmtContext.class,
125 f("Missing belongs-to statement in submodule:%s", submoduleName),
128 // check Yang version present, if not issue warning
129 checkYangVersion(ctx, submoduleName);
135 * <li>Identifier contains only permitted characters</li>
136 * <li>One Prefix statement child</li>
140 public void enterBelongs_to_stmt(Belongs_to_stmtContext ctx) {
141 String belongToName = getName(ctx);
142 String rootParentName = getRootParentName(ctx);
144 checkIdentifier(belongToName,
145 f("In (sub)module:%s , Belongs-to statement", rootParentName));
147 checkPresentChildOfType(
149 Prefix_stmtContext.class,
150 f("Missing prefix statement in belongs-to:%s, in (sub)module:%s",
151 belongToName, rootParentName), true);
157 * <li>At least one Revision statement present</li>
161 public void enterRevision_stmts(Revision_stmtsContext ctx) {
162 String rootParentName = getRootParentName(ctx);
164 checkPresentChildOfType(
166 Revision_stmtContext.class,
167 f("Missing at least one revision statement in (sub)module:%s",
168 rootParentName), false);
174 * <li>One Namespace statement present</li>
178 public void enterModule_header_stmts(Module_header_stmtsContext ctx) {
179 String moduleName = getRootParentName(ctx);
181 checkPresentChildOfType(ctx, Namespace_stmtContext.class,
182 f("Missing namespace statement in module:%s", moduleName), true);
184 // check Yang version present, if not issue warning
185 checkYangVersion(ctx, moduleName);
191 * <li>Namespace string can be parsed as URI</li>
195 public void enterNamespace_stmt(Namespace_stmtContext ctx) {
196 String namespaceName = getName(ctx);
197 String rootParentName = getRootParentName(ctx);
200 new URI(namespaceName);
201 } catch (URISyntaxException e) {
202 throw new YangValidationException(f(
203 "Namespace:%s in module:%s cannot be parsed as URI",
204 namespaceName, rootParentName));
211 * <li>Identifier contains only permitted characters</li>
212 * <li>Every import(identified by identifier) within a module/submodule is
213 * present only once</li>
214 * <li>One prefix statement child</li>
215 * <li>One revision-date statement child</li>
219 public void enterImport_stmt(Import_stmtContext ctx) {
220 String importName = getName(ctx);
221 String rootParentName = getRootParentName(ctx);
223 checkIdentifier(importName,
224 f("In (sub)module:%s , Import statement", rootParentName));
226 if (uniqueImports.contains(importName))
227 throw new YangValidationException(f(
228 "Module:%s imported twice in (sub)module:%s", importName,
230 uniqueImports.add(importName);
232 checkPresentChildOfType(
234 Prefix_stmtContext.class,
235 f("Missing prefix statement in import:%s, in (sub)module:%s",
236 importName, rootParentName), true);
237 checkPresentChildOfType(
239 Revision_date_stmtContext.class,
240 f("Missing revision-date statement in import:%s, in (sub)module:%s",
241 importName, rootParentName), true);
247 * <li>Date is in valid format</li>
251 public void enterRevision_date_stmt(Revision_date_stmtContext ctx) {
252 String rootParentName = getRootParentName(ctx);
253 String exceptionMessage = f(
254 "Invalid date format for revision-date:%s in import/include statement:%s, in (sub)module:%s , expected date format is:%s",
255 getName(ctx), getRootParentName(ctx), rootParentName,
256 YangModelParserListenerImpl.simpleDateFormat.format(new Date()));
258 validateDateFormat(getName(ctx),
259 YangModelParserListenerImpl.simpleDateFormat, exceptionMessage);
265 * <li>Identifier contains only permitted characters</li>
266 * <li>Every include(identified by identifier) within a module/submodule is
267 * present only once</li>
268 * <li>One Revision-date statement child</li>
272 public void enterInclude_stmt(Include_stmtContext ctx) {
273 String includeName = getName(ctx);
274 String rootParentName = getRootParentName(ctx);
276 checkIdentifier(includeName,
277 f("In (sub)module:%s , Include statement", rootParentName));
279 if (uniqueIncludes.contains(includeName))
280 throw new YangValidationException(f(
281 "Submodule:%s included twice in (sub)module:%s",
282 includeName, rootParentName));
283 uniqueIncludes.add(includeName);
285 checkPresentChildOfType(
287 Revision_date_stmtContext.class,
288 f("Missing revision-date statement in include:%s, in (sub)module:%s",
289 includeName, rootParentName), true);
292 static final String SUPPORTED_YANG_VERSION = "1";
297 * <li>Yang-version is specified as 1</li>
301 public void enterYang_version_stmt(YangParser.Yang_version_stmtContext ctx) {
302 String version = getName(ctx);
303 String rootParentName = getRootParentName(ctx);
304 if (!version.equals(SUPPORTED_YANG_VERSION)) {
305 throw new YangValidationException(
306 f("Unsupported yang version:%s, in (sub)module:%s, supported version:%s",
307 version, rootParentName, SUPPORTED_YANG_VERSION));
314 * <li>Date is in valid format</li>
318 public void enterRevision_stmt(YangParser.Revision_stmtContext ctx) {
319 String parentName = getRootParentName(ctx);
320 String exceptionMessage = f(
321 "Invalid date format for revision:%s in (sub)module:%s, expected date format is:%s",
322 getName(ctx), parentName,
323 YangModelParserListenerImpl.simpleDateFormat.format(new Date()));
325 validateDateFormat(getName(ctx),
326 YangModelParserListenerImpl.simpleDateFormat, exceptionMessage);
332 * <li>Identifier contains only permitted characters</li>
333 * <li>Every prefix(identified by identifier) within a module/submodule is
334 * presented only once</li>
338 public void enterPrefix_stmt(Prefix_stmtContext ctx) {
339 String name = getName(ctx);
342 f("In module or import statement:%s , Prefix",
343 getRootParentName(ctx)));
345 if (uniquePrefixes.contains(name))
346 throw new YangValidationException(f(
347 "Not a unique prefix:%s, in (sub)module:%s", name,
348 getRootParentName(ctx)));
349 uniquePrefixes.add(name);
352 private String getRootParentName(ParseTree ctx) {
353 ParseTree root = ctx;
354 while (root.getParent() != null) {
355 root = root.getParent();
357 return getName(root);
360 private static String getName(ParseTree child) {
361 return YangModelBuilderUtil.stringFromNode(child);
364 private static String f(String base, Object... args) {
365 return String.format(base, args);
368 private static void checkYangVersion(ParseTree ctx, String moduleName) {
369 if (!checkPresentChildOfType(ctx, Yang_version_stmtContext.class, true))
371 "Yang version statement not present in module:%s, Validating as yang version:%s",
372 moduleName, SUPPORTED_YANG_VERSION));
375 private static void validateDateFormat(String string, DateFormat format,
378 format.parse(string);
379 } catch (ParseException e) {
380 throw new YangValidationException(message);
384 private static Pattern identifierPattern = Pattern
385 .compile("[a-zA-Z_][a-zA-Z0-9_.-]*");
387 static void checkIdentifier(String name, String messagePrefix) {
388 if (!identifierPattern.matcher(name).matches())
389 throw new YangValidationException(f(
390 "%s identifier:%s is not in required format:%s",
391 messagePrefix, name, identifierPattern.toString()));
394 private static void checkPresentChildOfType(ParseTree ctx,
395 Class<?> expectedChildType, String message, boolean atMostOne) {
396 if (!checkPresentChildOfType(ctx, expectedChildType, atMostOne))
397 throw new YangValidationException(message);
400 private static boolean checkPresentChildOfType(ParseTree ctx,
401 Class<?> expectedChildType, boolean atMostOne) {
405 for (int i = 0; i < ctx.getChildCount(); i++) {
406 ParseTree child = ctx.getChild(i);
407 if (expectedChildType.isInstance(child))
411 return atMostOne ? count == 1 ? true : false : count != 0 ? true