690446456025e71217e68716d92750178e804bec
[yangtools.git] / parser / yang-test-util / src / main / java / org / opendaylight / yangtools / yang / test / util / YangParserTestUtils.java
1 /*
2  * Copyright (c) 2016 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.yangtools.yang.test.util;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
12 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
13 import java.io.File;
14 import java.io.FileFilter;
15 import java.io.IOException;
16 import java.net.URI;
17 import java.net.URISyntaxException;
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Locale;
24 import java.util.ServiceLoader;
25 import java.util.Set;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.opendaylight.yangtools.yang.common.QName;
28 import org.opendaylight.yangtools.yang.common.UnresolvedQName;
29 import org.opendaylight.yangtools.yang.common.YangConstants;
30 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
31 import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
32 import org.opendaylight.yangtools.yang.model.api.source.SourceRepresentation;
33 import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
34 import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
35 import org.opendaylight.yangtools.yang.model.spi.source.FileYangTextSource;
36 import org.opendaylight.yangtools.yang.model.spi.source.StringYangTextSource;
37 import org.opendaylight.yangtools.yang.model.spi.source.URLYangTextSource;
38 import org.opendaylight.yangtools.yang.parser.api.YangParser;
39 import org.opendaylight.yangtools.yang.parser.api.YangParserConfiguration;
40 import org.opendaylight.yangtools.yang.parser.api.YangParserException;
41 import org.opendaylight.yangtools.yang.parser.api.YangParserFactory;
42 import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException;
43
44 /**
45  * Utility class which provides convenience methods for producing effective schema context based on the supplied
46  * YANG/YIN sources or paths to these sources.
47  */
48 public final class YangParserTestUtils {
49     private static final FileFilter YANG_FILE_FILTER = file -> {
50         // Locale keeps SpotBugs happy. It should not matter that much anyway.
51         final String name = file.getName().toLowerCase(Locale.ENGLISH);
52         return name.endsWith(YangConstants.RFC6020_YANG_FILE_EXTENSION) && file.isFile();
53     };
54
55     private static final @NonNull YangParserFactory PARSER_FACTORY;
56
57     static {
58         final Iterator<@NonNull YangParserFactory> it = ServiceLoader.load(YangParserFactory.class).iterator();
59         if (!it.hasNext()) {
60             throw new IllegalStateException("No YangParserFactory found");
61         }
62         PARSER_FACTORY = it.next();
63     }
64
65     private YangParserTestUtils() {
66         // Hidden on purpose
67     }
68
69     /**
70      * Creates a new effective schema context containing the specified YANG source. Statement parser mode is set to
71      * default mode and all YANG features are supported.
72      *
73      * @param resource relative path to the YANG file to be parsed
74      *
75      * @return effective schema context
76      */
77     public static EffectiveModelContext parseYangResource(final String resource) {
78         return parseYangResource(resource, YangParserConfiguration.DEFAULT);
79     }
80
81     /**
82      * Creates a new effective schema context containing the specified YANG source. All YANG features are supported.
83      *
84      * @param resource relative path to the YANG file to be parsed
85      * @param config parser configuration
86      * @return effective schema context
87      */
88     public static EffectiveModelContext parseYangResource(final String resource, final YangParserConfiguration config) {
89         return parseYangResource(resource, config, null);
90     }
91
92     /**
93      * Creates a new effective schema context containing the specified YANG source. Statement parser mode is set to
94      * default mode.
95      *
96      * @param resource relative path to the YANG file to be parsed
97      * @param supportedFeatures set of supported features based on which all if-feature statements in the parsed YANG
98      *                          model are resolved
99      * @return effective schema context
100      */
101     public static EffectiveModelContext parseYangResource(final String resource, final Set<QName> supportedFeatures) {
102         return parseYangResource(resource, YangParserConfiguration.DEFAULT, supportedFeatures);
103     }
104
105     /**
106      * Creates a new effective schema context containing the specified YANG source.
107      *
108      * @param resource relative path to the YANG file to be parsed
109      * @param supportedFeatures set of supported features based on which all if-feature statements in the parsed YANG
110      *                          model are resolved
111      * @param config parser configuration
112      * @return effective schema context
113      */
114     public static EffectiveModelContext parseYangResource(final String resource, final YangParserConfiguration config,
115             final Set<QName> supportedFeatures) {
116         return parseYangSources(config, supportedFeatures,
117             new URLYangTextSource(YangParserTestUtils.class.getResource(resource)));
118     }
119
120     /**
121      * Creates a new effective schema context containing the specified YANG sources. Statement parser mode is set to
122      * default mode and all YANG features are supported.
123      *
124      * @param files YANG files to be parsed
125      * @return effective schema context
126      */
127     public static EffectiveModelContext parseYangFiles(final File... files) {
128         return parseYangFiles(Arrays.asList(files));
129     }
130
131     /**
132      * Creates a new effective schema context containing the specified YANG sources. Statement parser mode is set to
133      * default mode and all YANG features are supported.
134      *
135      * @param files collection of YANG files to be parsed
136      * @return effective schema context
137      */
138     public static EffectiveModelContext parseYangFiles(final Collection<File> files) {
139         return parseYangFiles(YangParserConfiguration.DEFAULT, files);
140     }
141
142     /**
143      * Creates a new effective schema context containing the specified YANG sources. Statement parser mode is set to
144      * default mode.
145      *
146      * @param supportedFeatures set of supported features based on which all if-feature statements in the parsed YANG
147      *                          models are resolved
148      * @param files YANG files to be parsed
149      * @return effective schema context
150      */
151     public static EffectiveModelContext parseYangFiles(final Set<QName> supportedFeatures, final File... files) {
152         return parseYangFiles(supportedFeatures, Arrays.asList(files));
153     }
154
155     public static EffectiveModelContext parseYangFiles(final Set<QName> supportedFeatures,
156             final Collection<File> files) {
157         return parseYangFiles(supportedFeatures, YangParserConfiguration.DEFAULT, files);
158     }
159
160     /**
161      * Creates a new effective schema context containing the specified YANG sources. All YANG features are supported.
162      *
163      * @param config parser configuration
164      * @param files YANG files to be parsed
165      * @return effective schema context
166      */
167     public static EffectiveModelContext parseYangFiles(final YangParserConfiguration config, final File... files) {
168         return parseYangFiles(config, Arrays.asList(files));
169     }
170
171     /**
172      * Creates a new effective schema context containing the specified YANG sources. All YANG features are supported.
173      *
174      * @param config parser configuration
175      * @param files collection of YANG files to be parsed
176      * @return effective schema context
177      */
178     public static EffectiveModelContext parseYangFiles(final YangParserConfiguration config,
179             final Collection<File> files) {
180         return parseYangFiles(null, config, files);
181     }
182
183     /**
184      * Creates a new effective schema context containing the specified YANG sources.
185      *
186      * @param supportedFeatures set of supported features based on which all if-feature statements in the parsed YANG
187      *                          models are resolved
188      * @param config parser configuration
189      * @param files YANG files to be parsed
190      * @return effective schema context
191      */
192     public static EffectiveModelContext parseYangFiles(final Set<QName> supportedFeatures,
193             final YangParserConfiguration config, final File... files) {
194         return parseYangFiles(supportedFeatures, config, Arrays.asList(files));
195     }
196
197     /**
198      * Creates a new effective schema context containing the specified YANG sources.
199      *
200      * @param supportedFeatures set of supported features based on which all if-feature statements in the parsed YANG
201      *                          models are resolved
202      * @param config parser configuration
203      * @param files YANG files to be parsed
204      * @return effective schema context
205      */
206     //  FIXME: use Java.nio.file.Path
207     public static EffectiveModelContext parseYangFiles(final Set<QName> supportedFeatures,
208             final YangParserConfiguration config, final Collection<File> files) {
209         return parseSources(config, supportedFeatures,
210             files.stream().map(file -> new FileYangTextSource(file.toPath())).toList());
211     }
212
213     /**
214      * Creates a new effective schema context containing the specified YANG sources. Statement parser mode is set to
215      * default mode and all YANG features are supported.
216      *
217      * @param resourcePath relative path to the directory with YANG files to be parsed
218      * @return effective schema context
219      */
220     public static EffectiveModelContext parseYangResourceDirectory(final String resourcePath) {
221         return parseYangResourceDirectory(resourcePath, YangParserConfiguration.DEFAULT);
222     }
223
224     /**
225      * Creates a new effective schema context containing the specified YANG sources. All YANG features are supported.
226      *
227      * @param resourcePath relative path to the directory with YANG files to be parsed
228      * @param config parser configuration
229      * @return effective schema context
230      */
231     public static EffectiveModelContext parseYangResourceDirectory(final String resourcePath,
232             final YangParserConfiguration config) {
233         return parseYangResourceDirectory(resourcePath, null, config);
234     }
235
236     /**
237      * Creates a new effective schema context containing the specified YANG sources. Statement parser mode is set to
238      * default mode.
239      *
240      * @param resourcePath relative path to the directory with YANG files to be parsed
241      * @param supportedFeatures set of supported features based on which all if-feature statements in the parsed YANG
242      *                          models are resolved
243      * @return effective schema context
244      */
245     public static EffectiveModelContext parseYangResourceDirectory(final String resourcePath,
246             final Set<QName> supportedFeatures) {
247         return parseYangResourceDirectory(resourcePath, supportedFeatures, YangParserConfiguration.DEFAULT);
248     }
249
250     /**
251      * Creates a new effective schema context containing the specified YANG sources.
252      *
253      * @param resourcePath relative path to the directory with YANG files to be parsed
254      * @param supportedFeatures set of supported features based on which all if-feature statements in the parsed YANG
255      *                          models are resolved
256      * @param config parser configuration
257      * @return effective schema context
258      */
259     @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Wrong inferent on listFiles")
260     public static EffectiveModelContext parseYangResourceDirectory(final String resourcePath,
261             final Set<QName> supportedFeatures, final YangParserConfiguration config) {
262         final URI directoryPath;
263         try {
264             directoryPath = YangParserTestUtils.class.getResource(resourcePath).toURI();
265         } catch (URISyntaxException e) {
266             throw new IllegalArgumentException("Failed to open resource " + resourcePath, e);
267         }
268         return parseYangFiles(supportedFeatures, config, new File(directoryPath).listFiles(YANG_FILE_FILTER));
269     }
270
271     /**
272      * Creates a new effective schema context containing the specified YANG sources. Statement parser mode is set to
273      * default mode and all YANG features are supported.
274      *
275      * @param clazz Resource lookup base
276      * @param resources Resource names to be looked up
277      * @return effective schema context
278      */
279     public static EffectiveModelContext parseYangResources(final Class<?> clazz, final String... resources) {
280         return parseYangResources(clazz, Arrays.asList(resources));
281     }
282
283     public static EffectiveModelContext parseYangResources(final Class<?> clazz, final Collection<String> resources) {
284         return parseSources(YangParserConfiguration.DEFAULT, null, resources.stream()
285             .map(resource -> new URLYangTextSource(clazz.getResource(resource)))
286             .toList());
287     }
288
289     /**
290      * Creates a new effective schema context containing the specified YANG sources. Statement parser mode is set to
291      * default mode.
292      *
293      * @param yangDirs relative paths to the directories containing YANG files to be parsed
294      * @param yangFiles relative paths to the YANG files to be parsed
295      * @param supportedFeatures set of supported features based on which all if-feature statements in the parsed YANG
296      *                          models are resolved
297      * @return effective schema context
298      */
299     public static EffectiveModelContext parseYangResources(final List<String> yangDirs, final List<String> yangFiles,
300             final Set<QName> supportedFeatures) {
301         return parseYangResources(yangDirs, yangFiles, supportedFeatures, YangParserConfiguration.DEFAULT);
302     }
303
304     /**
305      * Creates a new effective schema context containing the specified YANG sources. All YANG features are supported.
306      *
307      * @param yangResourceDirs relative paths to the directories containing YANG files to be parsed
308      * @param yangResources relative paths to the YANG files to be parsed
309      * @param config parser configuration
310      * @return effective schema context
311      */
312     public static EffectiveModelContext parseYangResources(final List<String> yangResourceDirs,
313             final List<String> yangResources, final YangParserConfiguration config) {
314         return parseYangResources(yangResourceDirs, yangResources, null, config);
315     }
316
317     /**
318      * Creates a new effective schema context containing the specified YANG sources.
319      *
320      * @param yangResourceDirs relative paths to the directories containing YANG files to be parsed
321      * @param yangResources relative paths to the YANG files to be parsed
322      * @param supportedFeatures set of supported features based on which all if-feature statements in the parsed YANG
323      *                          models are resolved
324      * @param config parser configuration
325      * @return effective schema context
326      */
327     public static EffectiveModelContext parseYangResources(final List<String> yangResourceDirs,
328             final List<String> yangResources, final Set<QName> supportedFeatures,
329             final YangParserConfiguration config) {
330         final List<File> allYangFiles = new ArrayList<>();
331         for (final String yangDir : yangResourceDirs) {
332             allYangFiles.addAll(getYangFiles(yangDir));
333         }
334
335         for (final String yangFile : yangResources) {
336             try {
337                 allYangFiles.add(new File(YangParserTestUtils.class.getResource(yangFile).toURI()));
338             } catch (URISyntaxException e) {
339                 throw new IllegalArgumentException("Invalid resource " + yangFile, e);
340             }
341         }
342
343         return parseYangFiles(supportedFeatures, config, allYangFiles);
344     }
345
346     public static EffectiveModelContext parseYangSources(final YangParserConfiguration config,
347             final Set<QName> supportedFeatures, final YangTextSource... sources) {
348         return parseSources(config, supportedFeatures, Arrays.asList(sources));
349     }
350
351     public static EffectiveModelContext parseSources(final YangParserConfiguration config,
352             final Set<QName> supportedFeatures, final Collection<? extends SourceRepresentation> sources) {
353         final YangParser parser = PARSER_FACTORY.createParser(config);
354         if (supportedFeatures != null) {
355             parser.setSupportedFeatures(FeatureSet.of(supportedFeatures));
356         }
357
358         try {
359             parser.addSources(sources);
360         } catch (YangSyntaxErrorException e) {
361             throw new IllegalArgumentException("Malformed source", e);
362         } catch (IOException e) {
363             throw new IllegalArgumentException("Failed to read a source", e);
364         }
365
366         try {
367             return parser.buildEffectiveModel();
368         } catch (YangParserException e) {
369             throw new IllegalStateException("Failed to assemble SchemaContext", e);
370         }
371     }
372
373     /**
374      * Creates a new effective schema context containing the specified YANG sources.
375      *
376      * @param sources list of yang sources in plain string
377      * @return effective schema context
378      */
379     public static EffectiveModelContext parseYang(final String... sources) {
380         return parseSources(YangParserConfiguration.DEFAULT, null,
381             Arrays.stream(sources).map(YangParserTestUtils::createYangTextSource).toList());
382     }
383
384     @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Wrong inferent on listFiles")
385     private static Collection<File> getYangFiles(final String resourcePath) {
386         final URI directoryPath;
387         try {
388             directoryPath = YangParserTestUtils.class.getResource(resourcePath).toURI();
389         } catch (URISyntaxException e) {
390             throw new IllegalArgumentException("Failed to open resource directory " + resourcePath, e);
391         }
392         return Arrays.asList(new File(directoryPath).listFiles(YANG_FILE_FILTER));
393     }
394
395
396     /**
397      * Create a new {@link YangTextSource} backed by a String input.
398      *
399      * @param sourceString YANG file as a String
400      * @return A new instance.
401      * @throws NullPointerException if {@code sourceString} is {@code null}
402      * @throws IllegalArgumentException if {@code sourceString} does not a valid YANG body, given a rather restrictive
403      *         view of what is valid.
404      */
405     private static @NonNull StringYangTextSource createYangTextSource(final String sourceString) {
406         // First line of a YANG file looks as follows:
407         //   `module module-name {`
408         // therefore in order to extract the name of the module from a plain string, we are interested in the second
409         // word of the first line
410         final var firstLine = sourceString.substring(0, sourceString.indexOf("{")).strip().split(" ");
411         final var moduleOrSubmoduleString = firstLine[0].strip();
412         checkArgument(moduleOrSubmoduleString.equals("module") || moduleOrSubmoduleString.equals("submodule"));
413
414         final String arg = firstLine[1].strip();
415         final var localName = UnresolvedQName.tryLocalName(arg);
416         checkArgument(localName != null);
417         return new StringYangTextSource(new SourceIdentifier(localName), sourceString, arg);
418     }
419 }