b8a5cf3050bc0a37359535bfc894dabf45784d93
[controller.git] / opendaylight / sal / yang-prototype / code-generator / yang-model-parser-impl / src / main / java / org / opendaylight / controller / yang / model / parser / impl / YangModelValidationListener.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.yang.model.parser.impl;
9
10 import java.net.URI;
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;
16 import java.util.Set;
17 import java.util.regex.Pattern;
18
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;
37
38 /**
39  * Validation listener that validates yang statements according to RFC-6020.
40  * This validator expects only one module or submodule per file.
41  */
42
43 /*
44  * TODO is this assumption(module per file) correct ? if so, should a check be
45  * performed ?
46  * 
47  * TODO break into smaller classes e.g. class for header statements, body
48  * statements...
49  */
50 final class YangModelValidationListener extends YangParserBaseListener {
51
52     private static final Logger logger = LoggerFactory
53             .getLogger(YangModelValidationListener.class);
54
55     private final Set<String> uniquePrefixes;
56     private final Set<String> uniqueImports;
57     private final Set<String> uniqueIncludes;
58
59     public YangModelValidationListener() {
60         super();
61         uniquePrefixes = new HashSet<String>();
62         uniqueImports = new HashSet<String>();
63         uniqueIncludes = new HashSet<String>();
64     }
65
66     /**
67      * Rules:
68      * <ol>
69      * <li>Identifier contains only permitted characters</li>
70      * <li>One revision statements present</li>
71      * <li>One header statements present</li>
72      * </ol>
73      */
74     @Override
75     public void enterModule_stmt(YangParser.Module_stmtContext ctx) {
76         String moduleName = getName(ctx);
77
78         checkIdentifier(moduleName, "Module");
79
80         checkPresentChildOfType(ctx, Revision_stmtsContext.class,
81                 f("Missing revision statements in module:%s", moduleName), true);
82
83         checkPresentChildOfType(ctx, Module_header_stmtsContext.class,
84                 f("Missing header statements in module:%s", moduleName), true);
85     }
86
87     /**
88      * Rules:
89      * <ol>
90      * <li>Identifier contains only permitted characters</li>
91      * <li>One revision statements present</li>
92      * <li>One header statements present</li>
93      * </ol>
94      */
95     @Override
96     public void enterSubmodule_stmt(Submodule_stmtContext ctx) {
97         String submoduleName = getName(ctx);
98
99         checkIdentifier(submoduleName, "Submodule");
100
101         checkPresentChildOfType(
102                 ctx,
103                 Revision_stmtsContext.class,
104                 f("Missing revision statements in submodule:%s", submoduleName),
105                 true);
106
107         checkPresentChildOfType(ctx, Submodule_header_stmtsContext.class,
108                 f("Missing header statements in submodule:%s", submoduleName),
109                 true);
110     }
111
112     /**
113      * Rules:
114      * <ol>
115      * <li>One Belongs-to statement present</li>
116      * </ol>
117      */
118     @Override
119     public void enterSubmodule_header_stmts(Submodule_header_stmtsContext ctx) {
120         String submoduleName = getRootParentName(ctx);
121
122         checkPresentChildOfType(
123                 ctx,
124                 Belongs_to_stmtContext.class,
125                 f("Missing belongs-to statement in submodule:%s", submoduleName),
126                 true);
127
128         // check Yang version present, if not issue warning
129         checkYangVersion(ctx, submoduleName);
130     }
131
132     /**
133      * Rules:
134      * <ol>
135      * <li>Identifier contains only permitted characters</li>
136      * <li>One Prefix statement child</li>
137      * </ol>
138      */
139     @Override
140     public void enterBelongs_to_stmt(Belongs_to_stmtContext ctx) {
141         String belongToName = getName(ctx);
142         String rootParentName = getRootParentName(ctx);
143
144         checkIdentifier(belongToName,
145                 f("In (sub)module:%s , Belongs-to statement", rootParentName));
146
147         checkPresentChildOfType(
148                 ctx,
149                 Prefix_stmtContext.class,
150                 f("Missing prefix statement in belongs-to:%s, in (sub)module:%s",
151                         belongToName, rootParentName), true);
152     }
153
154     /**
155      * Rules:
156      * <ol>
157      * <li>At least one Revision statement present</li>
158      * </ol>
159      */
160     @Override
161     public void enterRevision_stmts(Revision_stmtsContext ctx) {
162         String rootParentName = getRootParentName(ctx);
163
164         checkPresentChildOfType(
165                 ctx,
166                 Revision_stmtContext.class,
167                 f("Missing at least one revision statement in (sub)module:%s",
168                         rootParentName), false);
169     }
170
171     /**
172      * Rules:
173      * <ol>
174      * <li>One Namespace statement present</li>
175      * </ol>
176      */
177     @Override
178     public void enterModule_header_stmts(Module_header_stmtsContext ctx) {
179         String moduleName = getRootParentName(ctx);
180
181         checkPresentChildOfType(ctx, Namespace_stmtContext.class,
182                 f("Missing namespace statement in module:%s", moduleName), true);
183
184         // check Yang version present, if not issue warning
185         checkYangVersion(ctx, moduleName);
186     }
187
188     /**
189      * Rules:
190      * <ol>
191      * <li>Namespace string can be parsed as URI</li>
192      * </ol>
193      */
194     @Override
195     public void enterNamespace_stmt(Namespace_stmtContext ctx) {
196         String namespaceName = getName(ctx);
197         String rootParentName = getRootParentName(ctx);
198
199         try {
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));
205         }
206     }
207
208     /**
209      * Rules:
210      * <ol>
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>
216      * </ol>
217      */
218     @Override
219     public void enterImport_stmt(Import_stmtContext ctx) {
220         String importName = getName(ctx);
221         String rootParentName = getRootParentName(ctx);
222
223         checkIdentifier(importName,
224                 f("In (sub)module:%s , Import statement", rootParentName));
225
226         if (uniqueImports.contains(importName))
227             throw new YangValidationException(f(
228                     "Module:%s imported twice in (sub)module:%s", importName,
229                     rootParentName));
230         uniqueImports.add(importName);
231
232         checkPresentChildOfType(
233                 ctx,
234                 Prefix_stmtContext.class,
235                 f("Missing prefix statement in import:%s, in (sub)module:%s",
236                         importName, rootParentName), true);
237         checkPresentChildOfType(
238                 ctx,
239                 Revision_date_stmtContext.class,
240                 f("Missing revision-date statement in import:%s, in (sub)module:%s",
241                         importName, rootParentName), true);
242     }
243
244     /**
245      * Rules:
246      * <ol>
247      * <li>Date is in valid format</li>
248      * </ol>
249      */
250     @Override
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()));
257
258         validateDateFormat(getName(ctx),
259                 YangModelParserListenerImpl.simpleDateFormat, exceptionMessage);
260     }
261
262     /**
263      * Rules:
264      * <ol>
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>
269      * </ol>
270      */
271     @Override
272     public void enterInclude_stmt(Include_stmtContext ctx) {
273         String includeName = getName(ctx);
274         String rootParentName = getRootParentName(ctx);
275
276         checkIdentifier(includeName,
277                 f("In (sub)module:%s , Include statement", rootParentName));
278
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);
284
285         checkPresentChildOfType(
286                 ctx,
287                 Revision_date_stmtContext.class,
288                 f("Missing revision-date statement in include:%s, in (sub)module:%s",
289                         includeName, rootParentName), true);
290     }
291
292     static final String SUPPORTED_YANG_VERSION = "1";
293
294     /**
295      * Rules:
296      * <ol>
297      * <li>Yang-version is specified as 1</li>
298      * </ol>
299      */
300     @Override
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));
308         }
309     }
310
311     /**
312      * Rules:
313      * <ol>
314      * <li>Date is in valid format</li>
315      * </ol>
316      */
317     @Override
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()));
324
325         validateDateFormat(getName(ctx),
326                 YangModelParserListenerImpl.simpleDateFormat, exceptionMessage);
327     }
328
329     /**
330      * Rules:
331      * <ol>
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>
335      * </ol>
336      */
337     @Override
338     public void enterPrefix_stmt(Prefix_stmtContext ctx) {
339         String name = getName(ctx);
340         checkIdentifier(
341                 name,
342                 f("In module or import statement:%s , Prefix",
343                         getRootParentName(ctx)));
344
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);
350     }
351
352     private String getRootParentName(ParseTree ctx) {
353         ParseTree root = ctx;
354         while (root.getParent() != null) {
355             root = root.getParent();
356         }
357         return getName(root);
358     }
359
360     private static String getName(ParseTree child) {
361         return YangModelBuilderUtil.stringFromNode(child);
362     }
363
364     private static String f(String base, Object... args) {
365         return String.format(base, args);
366     }
367
368     private static void checkYangVersion(ParseTree ctx, String moduleName) {
369         if (!checkPresentChildOfType(ctx, Yang_version_stmtContext.class, true))
370             logger.warn(f(
371                     "Yang version statement not present in module:%s, Validating as yang version:%s",
372                     moduleName, SUPPORTED_YANG_VERSION));
373     }
374
375     private static void validateDateFormat(String string, DateFormat format,
376             String message) {
377         try {
378             format.parse(string);
379         } catch (ParseException e) {
380             throw new YangValidationException(message);
381         }
382     }
383
384     private static Pattern identifierPattern = Pattern
385             .compile("[a-zA-Z_][a-zA-Z0-9_.-]*");
386
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()));
392     }
393
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);
398     }
399
400     private static boolean checkPresentChildOfType(ParseTree ctx,
401             Class<?> expectedChildType, boolean atMostOne) {
402
403         int count = 0;
404
405         for (int i = 0; i < ctx.getChildCount(); i++) {
406             ParseTree child = ctx.getChild(i);
407             if (expectedChildType.isInstance(child))
408                 count++;
409         }
410
411         return atMostOne ? count == 1 ? true : false : count != 0 ? true
412                 : false;
413     }
414 }