2 * Copyright (c) 2016 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.yangtools.yang.test.util;
10 import static com.google.common.base.Preconditions.checkArgument;
12 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
14 import java.io.FileFilter;
15 import java.io.IOException;
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;
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;
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.
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();
55 private static final @NonNull YangParserFactory PARSER_FACTORY;
58 final Iterator<@NonNull YangParserFactory> it = ServiceLoader.load(YangParserFactory.class).iterator();
60 throw new IllegalStateException("No YangParserFactory found");
62 PARSER_FACTORY = it.next();
65 private YangParserTestUtils() {
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.
73 * @param resource relative path to the YANG file to be parsed
75 * @return effective schema context
77 public static EffectiveModelContext parseYangResource(final String resource) {
78 return parseYangResource(resource, YangParserConfiguration.DEFAULT);
82 * Creates a new effective schema context containing the specified YANG source. All YANG features are supported.
84 * @param resource relative path to the YANG file to be parsed
85 * @param config parser configuration
86 * @return effective schema context
88 public static EffectiveModelContext parseYangResource(final String resource, final YangParserConfiguration config) {
89 return parseYangResource(resource, config, null);
93 * Creates a new effective schema context containing the specified YANG source. Statement parser mode is set to
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
99 * @return effective schema context
101 public static EffectiveModelContext parseYangResource(final String resource, final Set<QName> supportedFeatures) {
102 return parseYangResource(resource, YangParserConfiguration.DEFAULT, supportedFeatures);
106 * Creates a new effective schema context containing the specified YANG source.
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
111 * @param config parser configuration
112 * @return effective schema context
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)));
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.
124 * @param files YANG files to be parsed
125 * @return effective schema context
127 public static EffectiveModelContext parseYangFiles(final File... files) {
128 return parseYangFiles(Arrays.asList(files));
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.
135 * @param files collection of YANG files to be parsed
136 * @return effective schema context
138 public static EffectiveModelContext parseYangFiles(final Collection<File> files) {
139 return parseYangFiles(YangParserConfiguration.DEFAULT, files);
143 * Creates a new effective schema context containing the specified YANG sources. Statement parser mode is set to
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
151 public static EffectiveModelContext parseYangFiles(final Set<QName> supportedFeatures, final File... files) {
152 return parseYangFiles(supportedFeatures, Arrays.asList(files));
155 public static EffectiveModelContext parseYangFiles(final Set<QName> supportedFeatures,
156 final Collection<File> files) {
157 return parseYangFiles(supportedFeatures, YangParserConfiguration.DEFAULT, files);
161 * Creates a new effective schema context containing the specified YANG sources. All YANG features are supported.
163 * @param config parser configuration
164 * @param files YANG files to be parsed
165 * @return effective schema context
167 public static EffectiveModelContext parseYangFiles(final YangParserConfiguration config, final File... files) {
168 return parseYangFiles(config, Arrays.asList(files));
172 * Creates a new effective schema context containing the specified YANG sources. All YANG features are supported.
174 * @param config parser configuration
175 * @param files collection of YANG files to be parsed
176 * @return effective schema context
178 public static EffectiveModelContext parseYangFiles(final YangParserConfiguration config,
179 final Collection<File> files) {
180 return parseYangFiles(null, config, files);
184 * Creates a new effective schema context containing the specified YANG sources.
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
192 public static EffectiveModelContext parseYangFiles(final Set<QName> supportedFeatures,
193 final YangParserConfiguration config, final File... files) {
194 return parseYangFiles(supportedFeatures, config, Arrays.asList(files));
198 * Creates a new effective schema context containing the specified YANG sources.
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
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());
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.
217 * @param resourcePath relative path to the directory with YANG files to be parsed
218 * @return effective schema context
220 public static EffectiveModelContext parseYangResourceDirectory(final String resourcePath) {
221 return parseYangResourceDirectory(resourcePath, YangParserConfiguration.DEFAULT);
225 * Creates a new effective schema context containing the specified YANG sources. All YANG features are supported.
227 * @param resourcePath relative path to the directory with YANG files to be parsed
228 * @param config parser configuration
229 * @return effective schema context
231 public static EffectiveModelContext parseYangResourceDirectory(final String resourcePath,
232 final YangParserConfiguration config) {
233 return parseYangResourceDirectory(resourcePath, null, config);
237 * Creates a new effective schema context containing the specified YANG sources. Statement parser mode is set to
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
245 public static EffectiveModelContext parseYangResourceDirectory(final String resourcePath,
246 final Set<QName> supportedFeatures) {
247 return parseYangResourceDirectory(resourcePath, supportedFeatures, YangParserConfiguration.DEFAULT);
251 * Creates a new effective schema context containing the specified YANG sources.
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
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;
264 directoryPath = YangParserTestUtils.class.getResource(resourcePath).toURI();
265 } catch (URISyntaxException e) {
266 throw new IllegalArgumentException("Failed to open resource " + resourcePath, e);
268 return parseYangFiles(supportedFeatures, config, new File(directoryPath).listFiles(YANG_FILE_FILTER));
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.
275 * @param clazz Resource lookup base
276 * @param resources Resource names to be looked up
277 * @return effective schema context
279 public static EffectiveModelContext parseYangResources(final Class<?> clazz, final String... resources) {
280 return parseYangResources(clazz, Arrays.asList(resources));
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)))
290 * Creates a new effective schema context containing the specified YANG sources. Statement parser mode is set to
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
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);
305 * Creates a new effective schema context containing the specified YANG sources. All YANG features are supported.
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
312 public static EffectiveModelContext parseYangResources(final List<String> yangResourceDirs,
313 final List<String> yangResources, final YangParserConfiguration config) {
314 return parseYangResources(yangResourceDirs, yangResources, null, config);
318 * Creates a new effective schema context containing the specified YANG sources.
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
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));
335 for (final String yangFile : yangResources) {
337 allYangFiles.add(new File(YangParserTestUtils.class.getResource(yangFile).toURI()));
338 } catch (URISyntaxException e) {
339 throw new IllegalArgumentException("Invalid resource " + yangFile, e);
343 return parseYangFiles(supportedFeatures, config, allYangFiles);
346 public static EffectiveModelContext parseYangSources(final YangParserConfiguration config,
347 final Set<QName> supportedFeatures, final YangTextSource... sources) {
348 return parseSources(config, supportedFeatures, Arrays.asList(sources));
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));
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);
367 return parser.buildEffectiveModel();
368 } catch (YangParserException e) {
369 throw new IllegalStateException("Failed to assemble SchemaContext", e);
374 * Creates a new effective schema context containing the specified YANG sources.
376 * @param sources list of yang sources in plain string
377 * @return effective schema context
379 public static EffectiveModelContext parseYang(final String... sources) {
380 return parseSources(YangParserConfiguration.DEFAULT, null,
381 Arrays.stream(sources).map(YangParserTestUtils::createYangTextSource).toList());
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;
388 directoryPath = YangParserTestUtils.class.getResource(resourcePath).toURI();
389 } catch (URISyntaxException e) {
390 throw new IllegalArgumentException("Failed to open resource directory " + resourcePath, e);
392 return Arrays.asList(new File(directoryPath).listFiles(YANG_FILE_FILTER));
397 * Create a new {@link YangTextSource} backed by a String input.
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.
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"));
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);