/* * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.controller.yang.model.parser.impl; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.opendaylight.controller.antlrv4.code.gen.YangLexer; import org.opendaylight.controller.antlrv4.code.gen.YangParser; import org.opendaylight.controller.yang.common.QName; import org.opendaylight.controller.yang.model.api.AugmentationSchema; import org.opendaylight.controller.yang.model.api.DataSchemaNode; import org.opendaylight.controller.yang.model.api.ExtensionDefinition; import org.opendaylight.controller.yang.model.api.Module; import org.opendaylight.controller.yang.model.api.ModuleImport; import org.opendaylight.controller.yang.model.api.NotificationDefinition; import org.opendaylight.controller.yang.model.api.RpcDefinition; import org.opendaylight.controller.yang.model.api.SchemaContext; import org.opendaylight.controller.yang.model.api.SchemaPath; import org.opendaylight.controller.yang.model.api.TypeDefinition; import org.opendaylight.controller.yang.model.api.type.BinaryTypeDefinition; import org.opendaylight.controller.yang.model.api.type.BitsTypeDefinition; import org.opendaylight.controller.yang.model.api.type.BitsTypeDefinition.Bit; import org.opendaylight.controller.yang.model.api.type.DecimalTypeDefinition; import org.opendaylight.controller.yang.model.api.type.IntegerTypeDefinition; import org.opendaylight.controller.yang.model.api.type.LengthConstraint; import org.opendaylight.controller.yang.model.api.type.PatternConstraint; import org.opendaylight.controller.yang.model.api.type.RangeConstraint; import org.opendaylight.controller.yang.model.api.type.StringTypeDefinition; import org.opendaylight.controller.yang.model.parser.api.YangModelParser; import org.opendaylight.controller.yang.model.parser.builder.api.AugmentationSchemaBuilder; import org.opendaylight.controller.yang.model.parser.builder.api.AugmentationTargetBuilder; import org.opendaylight.controller.yang.model.parser.builder.api.ChildNodeBuilder; import org.opendaylight.controller.yang.model.parser.builder.api.DataSchemaNodeBuilder; import org.opendaylight.controller.yang.model.parser.builder.api.TypeAwareBuilder; import org.opendaylight.controller.yang.model.parser.builder.api.TypeDefinitionBuilder; import org.opendaylight.controller.yang.model.parser.builder.impl.IdentitySchemaNodeBuilder; import org.opendaylight.controller.yang.model.parser.builder.impl.ModuleBuilder; import org.opendaylight.controller.yang.model.parser.builder.impl.UnionTypeBuilder; import org.opendaylight.controller.yang.model.util.BaseConstraints; import org.opendaylight.controller.yang.model.util.BinaryType; import org.opendaylight.controller.yang.model.util.BitsType; import org.opendaylight.controller.yang.model.util.StringType; import org.opendaylight.controller.yang.model.util.UnknownType; import org.opendaylight.controller.yang.model.util.YangTypesConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class YangModelParserImpl implements YangModelParser { private static final Logger logger = LoggerFactory .getLogger(YangModelParserImpl.class); @Override public Module parseYangModel(String yangFile) { final Map> modules = resolveModuleBuildersFromStreams(yangFile); Set result = build(modules); return result.iterator().next(); } @Override public Set parseYangModels(String... yangFiles) { final Map> modules = resolveModuleBuildersFromStreams(yangFiles); Set result = build(modules); return result; } @Override public Set parseYangModelsFromStreams( InputStream... yangModelStreams) { final Map> modules = resolveModuleBuildersFromStreams(yangModelStreams); Set result = build(modules); return result; } @Override public SchemaContext resolveSchemaContext(Set modules) { return new SchemaContextImpl(modules); } private Map> resolveModuleBuildersFromStreams( String... yangFiles) { InputStream[] streams = new InputStream[yangFiles.length]; for (int i = 0; i < yangFiles.length; i++) { final String yangFileName = yangFiles[i]; final File yangFile = new File(yangFileName); FileInputStream inStream = null; try { inStream = new FileInputStream(yangFile); } catch (FileNotFoundException e) { logger.warn("Exception while reading yang stream: " + inStream, e); } streams[i] = inStream; } return resolveModuleBuildersFromStreams(streams); } private Map> resolveModuleBuildersFromStreams( InputStream... yangFiles) { final Map> modules = new HashMap>(); final ParseTreeWalker walker = new ParseTreeWalker(); final List trees = parseStreams(yangFiles); final ModuleBuilder[] builders = new ModuleBuilder[trees.size()]; for (int i = 0; i < trees.size(); i++) { final YangModelParserListenerImpl yangModelParser = new YangModelParserListenerImpl(); walker.walk(yangModelParser, trees.get(i)); builders[i] = yangModelParser.getModuleBuilder(); } for (ModuleBuilder builder : builders) { final String builderName = builder.getName(); Date builderRevision = builder.getRevision(); if (builderRevision == null) { builderRevision = createEpochTime(); } TreeMap builderByRevision = modules .get(builderName); if (builderByRevision == null) { builderByRevision = new TreeMap(); } builderByRevision.put(builderRevision, builder); modules.put(builderName, builderByRevision); } return modules; } private List parseStreams(InputStream... yangStreams) { List trees = new ArrayList(); for (InputStream yangStream : yangStreams) { trees.add(parseStream(yangStream)); } return trees; } private ParseTree parseStream(InputStream yangStream) { ParseTree result = null; try { final ANTLRInputStream input = new ANTLRInputStream(yangStream); final YangLexer lexer = new YangLexer(input); final CommonTokenStream tokens = new CommonTokenStream(lexer); final YangParser parser = new YangParser(tokens); result = parser.yang(); } catch (IOException e) { logger.warn("Exception while reading yang file: " + yangStream, e); } return result; } private Set build(Map> modules) { // first validate for (Map.Entry> entry : modules .entrySet()) { for (Map.Entry childEntry : entry.getValue() .entrySet()) { ModuleBuilder moduleBuilder = childEntry.getValue(); validateBuilder(modules, moduleBuilder); } } // then build final Set result = new HashSet(); for (Map.Entry> entry : modules .entrySet()) { final Map modulesByRevision = new HashMap(); for (Map.Entry childEntry : entry.getValue() .entrySet()) { ModuleBuilder moduleBuilder = childEntry.getValue(); modulesByRevision.put(childEntry.getKey(), moduleBuilder.build()); result.add(moduleBuilder.build()); } } return result; } private void validateBuilder( Map> modules, ModuleBuilder builder) { resolveTypedefs(modules, builder); resolveAugments(modules, builder); resolveIdentities(modules, builder); } /** * Search for dirty nodes (node which contains UnknownType) and resolve * unknown types. * * @param modules * all available modules * @param module * current module */ private void resolveTypedefs( Map> modules, ModuleBuilder module) { Map, TypeAwareBuilder> dirtyNodes = module.getDirtyNodes(); if (dirtyNodes.size() == 0) { return; } else { for (Map.Entry, TypeAwareBuilder> entry : dirtyNodes .entrySet()) { TypeAwareBuilder typeToResolve = entry.getValue(); if (typeToResolve instanceof UnionTypeBuilder) { resolveUnionTypeBuilder(modules, module, (UnionTypeBuilder) typeToResolve); } else { UnknownType ut = (UnknownType) typeToResolve.getType(); TypeDefinition resolvedType = findTargetType(ut, modules, module); typeToResolve.setType(resolvedType); } } } } private UnionTypeBuilder resolveUnionTypeBuilder( Map> modules, ModuleBuilder builder, UnionTypeBuilder unionTypeBuilderToResolve) { List> resolvedTypes = new ArrayList>(); List> typesToRemove = new ArrayList>(); for (TypeDefinition td : unionTypeBuilderToResolve.getTypes()) { if (td instanceof UnknownType) { TypeDefinition resolvedType = findTargetType( (UnknownType) td, modules, builder); resolvedTypes.add(resolvedType); typesToRemove.add(td); } } List> unionTypeBuilderTypes = unionTypeBuilderToResolve .getTypes(); unionTypeBuilderTypes.addAll(resolvedTypes); unionTypeBuilderTypes.removeAll(typesToRemove); return unionTypeBuilderToResolve; } private TypeDefinition findTargetType(UnknownType ut, Map> modules, ModuleBuilder builder) { Map foundedTypeDefinitionBuilder = findTypeDefinitionBuilderWithConstraints( modules, ut, builder); TypeDefinitionBuilder targetType = foundedTypeDefinitionBuilder .entrySet().iterator().next().getKey(); TypeConstraints constraints = foundedTypeDefinitionBuilder.entrySet() .iterator().next().getValue(); TypeDefinition targetTypeBaseType = targetType.getBaseType(); // RANGE List ranges = ut.getRangeStatements(); resolveRanges(ranges, targetType, modules, builder); // LENGTH List lengths = ut.getLengthStatements(); resolveLengths(lengths, targetType, modules, builder); // PATTERN List patterns = ut.getPatterns(); // Fraction Digits Integer fractionDigits = ut.getFractionDigits(); targetTypeBaseType = mergeConstraints(targetTypeBaseType, constraints, ranges, lengths, patterns, fractionDigits); return targetTypeBaseType; } /** * Merge curent constraints with founded type constraints * * @param targetTypeBaseType * @param constraints * @param ranges * @param lengths * @param patterns * @param fractionDigits */ private TypeDefinition mergeConstraints(TypeDefinition targetTypeBaseType, TypeConstraints constraints, List ranges, List lengths, List patterns, Integer fractionDigits) { String targetTypeBaseTypeName = targetTypeBaseType.getQName() .getLocalName(); // enumeration, leafref and identityref omitted because they have no // restrictions if (targetTypeBaseType instanceof DecimalTypeDefinition) { List fullRanges = new ArrayList(); fullRanges.addAll(constraints.getRanges()); fullRanges.addAll(ranges); Integer fd = fractionDigits == null ? constraints .getFractionDigits() : fractionDigits; targetTypeBaseType = YangTypesConverter .javaTypeForBaseYangDecimal64Type(fullRanges, fd); } else if (targetTypeBaseType instanceof IntegerTypeDefinition) { List fullRanges = new ArrayList(); fullRanges.addAll(constraints.getRanges()); fullRanges.addAll(ranges); if (targetTypeBaseTypeName.startsWith("int")) { targetTypeBaseType = YangTypesConverter .javaTypeForBaseYangSignedIntegerType( targetTypeBaseTypeName, fullRanges); } else { targetTypeBaseType = YangTypesConverter .javaTypeForBaseYangUnsignedIntegerType( targetTypeBaseTypeName, fullRanges); } } else if (targetTypeBaseType instanceof StringTypeDefinition) { List fullLengths = new ArrayList(); fullLengths.addAll(constraints.getLengths()); fullLengths.addAll(lengths); List fullPatterns = new ArrayList(); fullPatterns.addAll(constraints.getPatterns()); fullPatterns.addAll(patterns); targetTypeBaseType = new StringType(fullLengths, fullPatterns); } else if (targetTypeBaseType instanceof BitsTypeDefinition) { BitsTypeDefinition bitsType = (BitsTypeDefinition) targetTypeBaseType; List bits = bitsType.getBits(); targetTypeBaseType = new BitsType(bits); } else if (targetTypeBaseType instanceof BinaryTypeDefinition) { targetTypeBaseType = new BinaryType(null, lengths, null); } return targetTypeBaseType; } private TypeDefinitionBuilder findTypeDefinitionBuilder( Map> modules, UnknownType unknownType, ModuleBuilder builder) { Map result = findTypeDefinitionBuilderWithConstraints( modules, unknownType, builder); return result.entrySet().iterator().next().getKey(); } private Map findTypeDefinitionBuilderWithConstraints( Map> modules, UnknownType unknownType, ModuleBuilder builder) { return findTypeDefinitionBuilderWithConstraints(new TypeConstraints(), modules, unknownType, builder); } /** * Traverse through all referenced types chain until base YANG type is * founded. * * @param constraints * current type constraints * @param modules * all available modules * @param unknownType * unknown type * @param builder * current module * @return map, where key is type referenced and value is its constraints */ private Map findTypeDefinitionBuilderWithConstraints( TypeConstraints constraints, Map> modules, UnknownType unknownType, ModuleBuilder builder) { Map result = new HashMap(); QName unknownTypeQName = unknownType.getQName(); String unknownTypeName = unknownTypeQName.getLocalName(); String unknownTypePrefix = unknownTypeQName.getPrefix(); // search for module which contains referenced typedef ModuleBuilder dependentModuleBuilder; if (unknownTypePrefix.equals(builder.getPrefix())) { dependentModuleBuilder = builder; } else { dependentModuleBuilder = findDependentModule(modules, builder, unknownTypePrefix); } // pull all typedef statements from dependent module... final Set typedefs = dependentModuleBuilder .getModuleTypedefs(); // and search for referenced typedef TypeDefinitionBuilder lookedUpBuilder = null; for (TypeDefinitionBuilder tdb : typedefs) { QName qname = tdb.getQName(); if (qname.getLocalName().equals(unknownTypeName)) { lookedUpBuilder = tdb; break; } } // if referenced type is UnknownType again, search recursively with // current constraints TypeDefinition referencedType = lookedUpBuilder.getBaseType(); if (referencedType instanceof UnknownType) { UnknownType unknown = (UnknownType) lookedUpBuilder.getBaseType(); final List ranges = unknown.getRangeStatements(); constraints.addRanges(ranges); final List lengths = unknown .getLengthStatements(); constraints.addLengths(lengths); final List patterns = unknown.getPatterns(); constraints.addPatterns(patterns); return findTypeDefinitionBuilderWithConstraints(constraints, modules, unknown, dependentModuleBuilder); } else { // pull restriction from this base type and add them to // 'constraints' if (referencedType instanceof DecimalTypeDefinition) { constraints.addRanges(((DecimalTypeDefinition) referencedType) .getRangeStatements()); constraints .setFractionDigits(((DecimalTypeDefinition) referencedType) .getFractionDigits()); } else if (referencedType instanceof IntegerTypeDefinition) { constraints.addRanges(((IntegerTypeDefinition) referencedType) .getRangeStatements()); } else if (referencedType instanceof StringTypeDefinition) { constraints.addPatterns(((StringTypeDefinition) referencedType) .getPatterns()); } else if (referencedType instanceof BinaryTypeDefinition) { constraints.addLengths(((BinaryTypeDefinition) referencedType) .getLengthConstraints()); } result.put(lookedUpBuilder, constraints); return result; } } /** * Go through all augmentation definitions and resolve them. This means find * referenced node and add child nodes to it. * * @param modules * all available modules * @param module * current module */ private void resolveAugments( Map> modules, ModuleBuilder module) { Set augmentBuilders = module .getAddedAugments(); Set augments = new HashSet(); for (AugmentationSchemaBuilder augmentBuilder : augmentBuilders) { SchemaPath augmentTargetSchemaPath = augmentBuilder.getTargetPath(); String prefix = null; List augmentTargetPath = new ArrayList(); for (QName pathPart : augmentTargetSchemaPath.getPath()) { prefix = pathPart.getPrefix(); augmentTargetPath.add(pathPart.getLocalName()); } ModuleBuilder dependentModule = findDependentModule(modules, module, prefix); // augmentTargetPath.add(0, dependentModule.getName()); // AugmentationTargetBuilder augmentTarget = (AugmentationTargetBuilder) dependentModule .getNode(augmentTargetPath); AugmentationSchema result = augmentBuilder.build(); augmentTarget.addAugmentation(result); fillAugmentTarget(augmentBuilder, (ChildNodeBuilder) augmentTarget); augments.add(result); } module.setAugmentations(augments); } /** * Add all augment's child nodes to given target. * * @param augment * @param target */ private void fillAugmentTarget(AugmentationSchemaBuilder augment, ChildNodeBuilder target) { for (DataSchemaNodeBuilder builder : augment.getChildNodes()) { builder.setAugmenting(true); target.addChildNode(builder); } } /** * Go through identity statements defined in current module and resolve * their 'base' statement if present. * * @param modules * all modules * @param module * module being resolved */ private void resolveIdentities( Map> modules, ModuleBuilder module) { Set identities = module.getAddedIdentities(); for (IdentitySchemaNodeBuilder identity : identities) { String baseIdentityName = identity.getBaseIdentityName(); if (baseIdentityName != null) { String baseIdentityPrefix = null; String baseIdentityLocalName = null; if (baseIdentityName.contains(":")) { String[] splitted = baseIdentityName.split(":"); baseIdentityPrefix = splitted[0]; baseIdentityLocalName = splitted[1]; } else { baseIdentityPrefix = module.getPrefix(); baseIdentityLocalName = baseIdentityName; } ModuleBuilder dependentModule; if (baseIdentityPrefix.equals(module.getPrefix())) { dependentModule = module; } else { dependentModule = findDependentModule(modules, module, baseIdentityPrefix); } Set dependentModuleIdentities = dependentModule .getAddedIdentities(); for (IdentitySchemaNodeBuilder idBuilder : dependentModuleIdentities) { if (idBuilder.getQName().getLocalName() .equals(baseIdentityLocalName)) { identity.setBaseIdentity(idBuilder); } } } } } /** * Find dependent module based on given prefix * * @param modules * all available modules * @param module * current module * @param prefix * target module prefix * @return dependent module builder */ private ModuleBuilder findDependentModule( Map> modules, ModuleBuilder module, String prefix) { ModuleImport dependentModuleImport = getModuleImport(module, prefix); String dependentModuleName = dependentModuleImport.getModuleName(); Date dependentModuleRevision = dependentModuleImport.getRevision(); TreeMap moduleBuildersByRevision = modules .get(dependentModuleName); ModuleBuilder dependentModule; if (dependentModuleRevision == null) { dependentModule = moduleBuildersByRevision.lastEntry().getValue(); } else { dependentModule = moduleBuildersByRevision .get(dependentModuleRevision); } return dependentModule; } /** * Get module import referenced by given prefix. * * @param builder * module to search * @param prefix * prefix associated with import * @return ModuleImport based on given prefix */ private ModuleImport getModuleImport(ModuleBuilder builder, String prefix) { ModuleImport moduleImport = null; for (ModuleImport mi : builder.getModuleImports()) { if (mi.getPrefix().equals(prefix)) { moduleImport = mi; break; } } return moduleImport; } /** * Helper method for resolving special 'min' or 'max' values in range * constraint * * @param ranges * ranges to resolve * @param targetType * target type * @param modules * all available modules * @param builder * current module */ private void resolveRanges(List ranges, TypeDefinitionBuilder targetType, Map> modules, ModuleBuilder builder) { if (ranges != null && ranges.size() > 0) { Long min = (Long) ranges.get(0).getMin(); Long max = (Long) ranges.get(ranges.size() - 1).getMax(); // if range contains one of the special values 'min' or 'max' if (min.equals(Long.MIN_VALUE) || max.equals(Long.MAX_VALUE)) { Long[] values = parseRangeConstraint(targetType, modules, builder); if (min.equals(Long.MIN_VALUE)) { min = values[0]; RangeConstraint oldFirst = ranges.get(0); RangeConstraint newFirst = BaseConstraints.rangeConstraint( min, oldFirst.getMax(), oldFirst.getDescription(), oldFirst.getReference()); ranges.set(0, newFirst); } if (max.equals(Long.MAX_VALUE)) { max = values[1]; RangeConstraint oldLast = ranges.get(ranges.size() - 1); RangeConstraint newLast = BaseConstraints.rangeConstraint( oldLast.getMin(), max, oldLast.getDescription(), oldLast.getReference()); ranges.set(ranges.size() - 1, newLast); } } } } /** * Helper method for resolving special 'min' or 'max' values in length * constraint * * @param lengths * lengths to resolve * @param targetType * target type * @param modules * all available modules * @param builder * current module */ private void resolveLengths(List lengths, TypeDefinitionBuilder targetType, Map> modules, ModuleBuilder builder) { if (lengths != null && lengths.size() > 0) { Long min = lengths.get(0).getMin().longValue(); Long max = lengths.get(lengths.size() - 1).getMax().longValue(); // if length contains one of the special values 'min' or 'max' if (min.equals(Long.MIN_VALUE) || max.equals(Long.MAX_VALUE)) { Long[] values = parseRangeConstraint(targetType, modules, builder); if (min.equals(Long.MIN_VALUE)) { min = values[0]; LengthConstraint oldFirst = lengths.get(0); LengthConstraint newFirst = BaseConstraints .lengthConstraint(min, oldFirst.getMax(), oldFirst.getDescription(), oldFirst.getReference()); lengths.set(0, newFirst); } if (max.equals(Long.MAX_VALUE)) { max = values[1]; LengthConstraint oldLast = lengths.get(lengths.size() - 1); LengthConstraint newLast = BaseConstraints .lengthConstraint(oldLast.getMin(), max, oldLast.getDescription(), oldLast.getReference()); lengths.set(lengths.size() - 1, newLast); } } } } private Long[] parseRangeConstraint(TypeDefinitionBuilder targetType, Map> modules, ModuleBuilder builder) { TypeDefinition targetBaseType = targetType.getBaseType(); if (targetBaseType instanceof IntegerTypeDefinition) { IntegerTypeDefinition itd = (IntegerTypeDefinition) targetBaseType; List ranges = itd.getRangeStatements(); Long min = (Long) ranges.get(0).getMin(); Long max = (Long) ranges.get(ranges.size() - 1).getMax(); return new Long[] { min, max }; } else if (targetBaseType instanceof DecimalTypeDefinition) { DecimalTypeDefinition dtd = (DecimalTypeDefinition) targetBaseType; List ranges = dtd.getRangeStatements(); Long min = (Long) ranges.get(0).getMin(); Long max = (Long) ranges.get(ranges.size() - 1).getMax(); return new Long[] { min, max }; } else { return parseRangeConstraint( findTypeDefinitionBuilder(modules, (UnknownType) targetBaseType, builder), modules, builder); } } private Date createEpochTime() { Calendar c = Calendar.getInstance(); c.setTimeInMillis(0); return c.getTime(); } private static class SchemaContextImpl implements SchemaContext { private final Set modules; private SchemaContextImpl(Set modules) { this.modules = modules; } @Override public Set getDataDefinitions() { final Set dataDefs = new HashSet(); for (Module m : modules) { dataDefs.addAll(m.getChildNodes()); } return dataDefs; } @Override public Set getModules() { return modules; } @Override public Set getNotifications() { final Set notifications = new HashSet(); for (Module m : modules) { notifications.addAll(m.getNotifications()); } return notifications; } @Override public Set getOperations() { final Set rpcs = new HashSet(); for (Module m : modules) { rpcs.addAll(m.getRpcs()); } return rpcs; } @Override public Set getExtensions() { final Set extensions = new HashSet(); for (Module m : modules) { extensions.addAll(m.getExtensionSchemaNodes()); } return extensions; } } private static class TypeConstraints { private final List ranges = new ArrayList(); private final List lengths = new ArrayList(); private final List patterns = new ArrayList(); private Integer fractionDigits; public List getRanges() { return ranges; } public void addRanges(List ranges) { this.ranges.addAll(0, ranges); } public List getLengths() { return lengths; } public void addLengths(List lengths) { this.lengths.addAll(0, lengths); } public List getPatterns() { return patterns; } public void addPatterns(List patterns) { this.patterns.addAll(0, patterns); } public Integer getFractionDigits() { return fractionDigits; } public void setFractionDigits(Integer fractionDigits) { if (fractionDigits != null) { this.fractionDigits = fractionDigits; } } } }