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.io.FileInputStream;
12 import java.io.IOException;
13 import java.util.ArrayList;
14 import java.util.Calendar;
15 import java.util.Date;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.List;
21 import java.util.TreeMap;
23 import org.antlr.v4.runtime.ANTLRInputStream;
24 import org.antlr.v4.runtime.CommonTokenStream;
25 import org.antlr.v4.runtime.tree.ParseTree;
26 import org.antlr.v4.runtime.tree.ParseTreeWalker;
27 import org.opendaylight.controller.antlrv4.code.gen.YangLexer;
28 import org.opendaylight.controller.antlrv4.code.gen.YangParser;
29 import org.opendaylight.controller.model.api.type.BinaryTypeDefinition;
30 import org.opendaylight.controller.model.api.type.BitsTypeDefinition;
31 import org.opendaylight.controller.model.api.type.BitsTypeDefinition.Bit;
32 import org.opendaylight.controller.model.api.type.DecimalTypeDefinition;
33 import org.opendaylight.controller.model.api.type.InstanceIdentifierTypeDefinition;
34 import org.opendaylight.controller.model.api.type.IntegerTypeDefinition;
35 import org.opendaylight.controller.model.api.type.LengthConstraint;
36 import org.opendaylight.controller.model.api.type.PatternConstraint;
37 import org.opendaylight.controller.model.api.type.RangeConstraint;
38 import org.opendaylight.controller.model.api.type.StringTypeDefinition;
39 import org.opendaylight.controller.model.util.BaseConstraints;
40 import org.opendaylight.controller.model.util.BinaryType;
41 import org.opendaylight.controller.model.util.BitsType;
42 import org.opendaylight.controller.model.util.StringType;
43 import org.opendaylight.controller.model.util.UnknownType;
44 import org.opendaylight.controller.model.util.YangTypesConverter;
45 import org.opendaylight.controller.yang.common.QName;
46 import org.opendaylight.controller.yang.model.api.AugmentationSchema;
47 import org.opendaylight.controller.yang.model.api.DataSchemaNode;
48 import org.opendaylight.controller.yang.model.api.ExtensionDefinition;
49 import org.opendaylight.controller.yang.model.api.Module;
50 import org.opendaylight.controller.yang.model.api.ModuleImport;
51 import org.opendaylight.controller.yang.model.api.NotificationDefinition;
52 import org.opendaylight.controller.yang.model.api.RpcDefinition;
53 import org.opendaylight.controller.yang.model.api.SchemaContext;
54 import org.opendaylight.controller.yang.model.api.SchemaPath;
55 import org.opendaylight.controller.yang.model.api.TypeDefinition;
56 import org.opendaylight.controller.yang.model.parser.api.YangModelParser;
57 import org.opendaylight.controller.yang.model.parser.builder.api.AugmentationSchemaBuilder;
58 import org.opendaylight.controller.yang.model.parser.builder.api.AugmentationTargetBuilder;
59 import org.opendaylight.controller.yang.model.parser.builder.api.ChildNodeBuilder;
60 import org.opendaylight.controller.yang.model.parser.builder.api.DataSchemaNodeBuilder;
61 import org.opendaylight.controller.yang.model.parser.builder.api.TypeAwareBuilder;
62 import org.opendaylight.controller.yang.model.parser.builder.api.TypeDefinitionBuilder;
63 import org.opendaylight.controller.yang.model.parser.builder.impl.ModuleBuilder;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
67 public class YangModelParserImpl implements YangModelParser {
69 private static final Logger logger = LoggerFactory
70 .getLogger(YangModelParserImpl.class);
73 public Module parseYangModel(String yangFile) {
74 final Map<String, TreeMap<Date, ModuleBuilder>> modules = loadFiles(yangFile);
75 Set<Module> result = build(modules);
76 return result.iterator().next();
80 public Set<Module> parseYangModels(String... yangFiles) {
81 final Map<String, TreeMap<Date, ModuleBuilder>> modules = loadFiles(yangFiles);
82 Set<Module> result = build(modules);
87 public SchemaContext resolveSchemaContext(Set<Module> modules) {
88 return new SchemaContextImpl(modules);
91 private Map<String, TreeMap<Date, ModuleBuilder>> loadFiles(
92 String... yangFiles) {
93 final Map<String, TreeMap<Date, ModuleBuilder>> modules = new HashMap<String, TreeMap<Date, ModuleBuilder>>();
95 final YangModelParserListenerImpl yangModelParser = new YangModelParserListenerImpl();
96 final ParseTreeWalker walker = new ParseTreeWalker();
98 List<ParseTree> trees = parseFiles(yangFiles);
100 ModuleBuilder[] builders = new ModuleBuilder[trees.size()];
102 for (int i = 0; i < trees.size(); i++) {
103 walker.walk(yangModelParser, trees.get(i));
104 builders[i] = yangModelParser.getModuleBuilder();
107 for (ModuleBuilder builder : builders) {
108 final String builderName = builder.getName();
109 Date builderRevision = builder.getRevision();
110 if (builderRevision == null) {
111 builderRevision = createEpochTime();
114 TreeMap<Date, ModuleBuilder> builderByRevision = modules
116 if (builderByRevision == null) {
117 builderByRevision = new TreeMap<Date, ModuleBuilder>();
119 builderByRevision.put(builderRevision, builder);
121 modules.put(builderName, builderByRevision);
126 private List<ParseTree> parseFiles(String... yangFileNames) {
127 List<ParseTree> trees = new ArrayList<ParseTree>();
128 for (String fileName : yangFileNames) {
129 trees.add(parseFile(fileName));
134 private ParseTree parseFile(String yangFileName) {
135 ParseTree result = null;
137 final File yangFile = new File(yangFileName);
138 final FileInputStream inStream = new FileInputStream(yangFile);
139 final ANTLRInputStream input = new ANTLRInputStream(inStream);
140 final YangLexer lexer = new YangLexer(input);
141 final CommonTokenStream tokens = new CommonTokenStream(lexer);
142 final YangParser parser = new YangParser(tokens);
143 result = parser.yang();
144 } catch (IOException e) {
145 logger.warn("Exception while reading yang file: " + yangFileName, e);
150 private Set<Module> build(Map<String, TreeMap<Date, ModuleBuilder>> modules) {
152 for (Map.Entry<String, TreeMap<Date, ModuleBuilder>> entry : modules
154 for (Map.Entry<Date, ModuleBuilder> childEntry : entry.getValue()
156 ModuleBuilder moduleBuilder = childEntry.getValue();
157 validateBuilder(modules, moduleBuilder);
161 final Set<Module> result = new HashSet<Module>();
162 for (Map.Entry<String, TreeMap<Date, ModuleBuilder>> entry : modules
164 final Map<Date, Module> modulesByRevision = new HashMap<Date, Module>();
165 for (Map.Entry<Date, ModuleBuilder> childEntry : entry.getValue()
167 ModuleBuilder moduleBuilder = childEntry.getValue();
168 modulesByRevision.put(childEntry.getKey(),
169 moduleBuilder.build());
170 result.add(moduleBuilder.build());
177 private void validateBuilder(
178 Map<String, TreeMap<Date, ModuleBuilder>> modules,
179 ModuleBuilder builder) {
180 resolveTypedefs(modules, builder);
181 resolveAugments(modules, builder);
185 * Search for dirty nodes (node which contains UnknownType) and resolve
189 * all available modules
193 private void resolveTypedefs(
194 Map<String, TreeMap<Date, ModuleBuilder>> modules,
195 ModuleBuilder builder) {
196 Map<List<String>, TypeAwareBuilder> dirtyNodes = builder
198 if (dirtyNodes.size() == 0) {
201 for (Map.Entry<List<String>, TypeAwareBuilder> entry : dirtyNodes
203 TypeAwareBuilder typeToResolve = entry.getValue();
204 Map<TypeDefinitionBuilder, TypeConstraints> foundedTypeDefinitionBuilder = findTypeDefinitionBuilderWithConstraints(
205 modules, entry.getValue(), builder);
206 TypeDefinitionBuilder targetType = foundedTypeDefinitionBuilder
207 .entrySet().iterator().next().getKey();
208 TypeConstraints constraints = foundedTypeDefinitionBuilder
209 .entrySet().iterator().next().getValue();
211 UnknownType ut = (UnknownType) typeToResolve.getType();
214 List<RangeConstraint> ranges = ut.getRangeStatements();
215 resolveRanges(ranges, typeToResolve, targetType, modules,
219 List<LengthConstraint> lengths = ut.getLengthStatements();
220 resolveLengths(lengths, typeToResolve, targetType, modules,
224 List<PatternConstraint> patterns = ut.getPatterns();
227 Integer fractionDigits = ut.getFractionDigits();
229 TypeDefinition<?> type = targetType.getBaseType();
230 String typeName = type.getQName().getLocalName();
232 // MERGE CONSTRAINTS (enumeration and leafref omitted because
233 // they have no restrictions)
234 if (type instanceof DecimalTypeDefinition) {
235 List<RangeConstraint> fullRanges = new ArrayList<RangeConstraint>();
236 fullRanges.addAll(constraints.getRanges());
237 fullRanges.addAll(ranges);
238 Integer fd = fractionDigits == null ? constraints
239 .getFractionDigits() : fractionDigits;
240 type = YangTypesConverter.javaTypeForBaseYangDecimal64Type(
242 } else if (type instanceof IntegerTypeDefinition) {
243 List<RangeConstraint> fullRanges = new ArrayList<RangeConstraint>();
244 fullRanges.addAll(constraints.getRanges());
245 fullRanges.addAll(ranges);
246 if (typeName.startsWith("int")) {
247 type = YangTypesConverter
248 .javaTypeForBaseYangSignedIntegerType(typeName,
251 type = YangTypesConverter
252 .javaTypeForBaseYangUnsignedIntegerType(
253 typeName, fullRanges);
255 } else if (type instanceof StringTypeDefinition) {
256 List<LengthConstraint> fullLengths = new ArrayList<LengthConstraint>();
257 fullLengths.addAll(constraints.getLengths());
258 fullLengths.addAll(lengths);
259 List<PatternConstraint> fullPatterns = new ArrayList<PatternConstraint>();
260 fullPatterns.addAll(constraints.getPatterns());
261 fullPatterns.addAll(patterns);
262 type = new StringType(fullLengths, fullPatterns);
263 } else if (type instanceof BitsTypeDefinition) {
264 // TODO: add 'length' restriction to BitsType
265 BitsTypeDefinition bitsType = (BitsTypeDefinition) type;
266 List<Bit> bits = bitsType.getBits();
267 type = new BitsType(bits);
268 } else if (type instanceof BinaryTypeDefinition) {
269 type = new BinaryType(null, lengths, null);
270 } else if (typeName.equals("instance-identifier")) {
271 // TODO: instance-identifier
273 * boolean requireInstance = isRequireInstance(typeBody);
274 * type = new InstanceIdentifier(null, requireInstance);
277 typeToResolve.setType(type);
282 private TypeDefinitionBuilder findTypeDefinitionBuilder(
283 Map<String, TreeMap<Date, ModuleBuilder>> modules,
284 TypeAwareBuilder typeBuilder, ModuleBuilder builder) {
285 Map<TypeDefinitionBuilder, TypeConstraints> result = findTypeDefinitionBuilderWithConstraints(
286 modules, typeBuilder, builder);
287 return result.entrySet().iterator().next().getKey();
290 private Map<TypeDefinitionBuilder, TypeConstraints> findTypeDefinitionBuilderWithConstraints(
291 Map<String, TreeMap<Date, ModuleBuilder>> modules,
292 TypeAwareBuilder typeBuilder, ModuleBuilder builder) {
293 return findTypeDefinitionBuilderWithConstraints(new TypeConstraints(),
294 modules, typeBuilder, builder);
298 * Traverse through all referenced types chain until base YANG type is
302 * current type constraints
304 * all available modules
306 * type builder which contains type
309 * @return map, where key is type referenced and value is its constraints
311 private Map<TypeDefinitionBuilder, TypeConstraints> findTypeDefinitionBuilderWithConstraints(
312 TypeConstraints constraints,
313 Map<String, TreeMap<Date, ModuleBuilder>> modules,
314 TypeAwareBuilder typeBuilder, ModuleBuilder builder) {
315 Map<TypeDefinitionBuilder, TypeConstraints> result = new HashMap<TypeDefinitionBuilder, TypeConstraints>();
317 UnknownType type = (UnknownType) typeBuilder.getType();
318 QName typeQName = type.getQName();
319 String typeName = type.getQName().getLocalName();
320 String prefix = typeQName.getPrefix();
322 // search for module which contains referenced typedef
323 ModuleBuilder dependentModuleBuilder;
324 if (prefix.equals(builder.getPrefix())) {
325 dependentModuleBuilder = builder;
327 ModuleImport dependentModuleImport = getModuleImport(builder,
329 String dependentModuleName = dependentModuleImport.getModuleName();
330 Date dependentModuleRevision = dependentModuleImport.getRevision();
331 TreeMap<Date, ModuleBuilder> moduleBuildersByRevision = modules
332 .get(dependentModuleName);
333 if (dependentModuleRevision == null) {
334 dependentModuleBuilder = moduleBuildersByRevision.lastEntry()
337 dependentModuleBuilder = moduleBuildersByRevision
338 .get(dependentModuleRevision);
342 // pull all typedef statements from dependent module...
343 final Set<TypeDefinitionBuilder> typedefs = dependentModuleBuilder
344 .getModuleTypedefs();
345 // and search for referenced typedef
346 TypeDefinitionBuilder lookedUpBuilder = null;
347 for (TypeDefinitionBuilder tdb : typedefs) {
348 QName qname = tdb.getQName();
349 if (qname.getLocalName().equals(typeName)) {
350 lookedUpBuilder = tdb;
355 // if referenced type is UnknownType again, search recursively with
356 // current constraints
357 TypeDefinition<?> referencedType = lookedUpBuilder.getBaseType();
358 if (referencedType instanceof UnknownType) {
359 UnknownType unknown = (UnknownType) lookedUpBuilder.getBaseType();
361 final List<RangeConstraint> ranges = unknown.getRangeStatements();
362 constraints.addRanges(ranges);
363 final List<LengthConstraint> lengths = unknown
364 .getLengthStatements();
365 constraints.addLengths(lengths);
366 final List<PatternConstraint> patterns = unknown.getPatterns();
367 constraints.addPatterns(patterns);
368 return findTypeDefinitionBuilderWithConstraints(constraints,
369 modules, (TypeAwareBuilder) lookedUpBuilder,
370 dependentModuleBuilder);
372 // pull restriction from this base type and add them to
374 if (referencedType instanceof DecimalTypeDefinition) {
375 constraints.addRanges(((DecimalTypeDefinition) referencedType)
376 .getRangeStatements());
378 .setFractionDigits(((DecimalTypeDefinition) referencedType)
379 .getFractionDigits());
380 } else if (referencedType instanceof IntegerTypeDefinition) {
381 constraints.addRanges(((IntegerTypeDefinition) referencedType)
382 .getRangeStatements());
383 } else if (referencedType instanceof StringTypeDefinition) {
384 constraints.addPatterns(((StringTypeDefinition) referencedType)
386 } else if (referencedType instanceof BitsTypeDefinition) {
387 // TODO: add 'length' restriction to BitsType
388 } else if (referencedType instanceof BinaryTypeDefinition) {
390 } else if (referencedType instanceof InstanceIdentifierTypeDefinition) {
391 // TODO: instance-identifier
394 result.put(lookedUpBuilder, constraints);
395 // return lookedUpBuilder;
401 * Go through all augmentation definitions and resolve them. This means find
402 * referenced node and add child nodes to it.
405 * all available modules
409 private void resolveAugments(
410 Map<String, TreeMap<Date, ModuleBuilder>> modules,
411 ModuleBuilder builder) {
412 Set<AugmentationSchemaBuilder> augmentBuilders = builder
415 Set<AugmentationSchema> augments = new HashSet<AugmentationSchema>();
416 for (AugmentationSchemaBuilder augmentBuilder : augmentBuilders) {
417 SchemaPath augmentTargetSchemaPath = augmentBuilder.getTargetPath();
418 String prefix = null;
419 List<String> augmentTargetPath = new ArrayList<String>();
420 for (QName pathPart : augmentTargetSchemaPath.getPath()) {
421 prefix = pathPart.getPrefix();
422 augmentTargetPath.add(pathPart.getLocalName());
424 ModuleImport dependentModuleImport = getModuleImport(builder,
426 String dependentModuleName = dependentModuleImport.getModuleName();
427 augmentTargetPath.add(0, dependentModuleName);
429 Date dependentModuleRevision = dependentModuleImport.getRevision();
431 TreeMap<Date, ModuleBuilder> moduleBuildersByRevision = modules
432 .get(dependentModuleName);
433 ModuleBuilder dependentModule;
434 if (dependentModuleRevision == null) {
435 dependentModule = moduleBuildersByRevision.lastEntry()
438 dependentModule = moduleBuildersByRevision
439 .get(dependentModuleRevision);
442 AugmentationTargetBuilder augmentTarget = (AugmentationTargetBuilder) dependentModule
443 .getNode(augmentTargetPath);
444 AugmentationSchema result = augmentBuilder.build();
445 augmentTarget.addAugmentation(result);
446 fillAugmentTarget(augmentBuilder, (ChildNodeBuilder) augmentTarget);
447 augments.add(result);
449 builder.setAugmentations(augments);
453 * Add all augment's child nodes to given target.
458 private void fillAugmentTarget(AugmentationSchemaBuilder augment,
459 ChildNodeBuilder target) {
460 for (DataSchemaNodeBuilder builder : augment.getChildNodes()) {
461 builder.setAugmenting(true);
462 target.addChildNode(builder);
467 * Get module import referenced by given prefix.
472 * prefix associated with import
473 * @return ModuleImport based on given prefix
475 private ModuleImport getModuleImport(ModuleBuilder builder, String prefix) {
476 ModuleImport moduleImport = null;
477 for (ModuleImport mi : builder.getModuleImports()) {
478 if (mi.getPrefix().equals(prefix)) {
487 * Helper method for resolving special 'min' or 'max' values in range
492 * @param typeToResolve
497 * all available modules
501 private void resolveRanges(List<RangeConstraint> ranges,
502 TypeAwareBuilder typeToResolve, TypeDefinitionBuilder targetType,
503 Map<String, TreeMap<Date, ModuleBuilder>> modules,
504 ModuleBuilder builder) {
505 if (ranges != null && ranges.size() > 0) {
506 Long min = (Long) ranges.get(0).getMin();
507 Long max = (Long) ranges.get(ranges.size() - 1).getMax();
508 // if range contains one of the special values 'min' or 'max'
509 if (min.equals(Long.MIN_VALUE) || max.equals(Long.MAX_VALUE)) {
510 Long[] values = parseRangeConstraint(typeToResolve, targetType,
512 if (min.equals(Long.MIN_VALUE)) {
514 RangeConstraint oldFirst = ranges.get(0);
515 RangeConstraint newFirst = BaseConstraints.rangeConstraint(
516 min, oldFirst.getMax(), oldFirst.getDescription(),
517 oldFirst.getReference());
518 ranges.set(0, newFirst);
520 if (max.equals(Long.MAX_VALUE)) {
522 RangeConstraint oldLast = ranges.get(ranges.size() - 1);
523 RangeConstraint newLast = BaseConstraints.rangeConstraint(
524 oldLast.getMin(), max, oldLast.getDescription(),
525 oldLast.getReference());
526 ranges.set(ranges.size() - 1, newLast);
533 * Helper method for resolving special 'min' or 'max' values in length
538 * @param typeToResolve
543 * all available modules
547 private void resolveLengths(List<LengthConstraint> lengths,
548 TypeAwareBuilder typeToResolve, TypeDefinitionBuilder targetType,
549 Map<String, TreeMap<Date, ModuleBuilder>> modules,
550 ModuleBuilder builder) {
551 if (lengths != null && lengths.size() > 0) {
552 Long min = lengths.get(0).getMin();
553 Long max = lengths.get(lengths.size() - 1).getMax();
554 // if length contains one of the special values 'min' or 'max'
555 if (min.equals(Long.MIN_VALUE) || max.equals(Long.MAX_VALUE)) {
556 Long[] values = parseRangeConstraint(typeToResolve, targetType,
558 if (min.equals(Long.MIN_VALUE)) {
560 LengthConstraint oldFirst = lengths.get(0);
561 LengthConstraint newFirst = BaseConstraints
562 .lengthConstraint(min, oldFirst.getMax(),
563 oldFirst.getDescription(),
564 oldFirst.getReference());
565 lengths.set(0, newFirst);
567 if (max.equals(Long.MAX_VALUE)) {
569 LengthConstraint oldLast = lengths.get(lengths.size() - 1);
570 LengthConstraint newLast = BaseConstraints
571 .lengthConstraint(oldLast.getMin(), max,
572 oldLast.getDescription(),
573 oldLast.getReference());
574 lengths.set(lengths.size() - 1, newLast);
580 private Long[] parseRangeConstraint(TypeAwareBuilder typeToResolve,
581 TypeDefinitionBuilder targetType,
582 Map<String, TreeMap<Date, ModuleBuilder>> modules,
583 ModuleBuilder builder) {
584 TypeDefinition<?> targetBaseType = targetType.getBaseType();
586 if (targetBaseType instanceof IntegerTypeDefinition) {
587 IntegerTypeDefinition itd = (IntegerTypeDefinition) targetBaseType;
588 List<RangeConstraint> ranges = itd.getRangeStatements();
589 Long min = (Long) ranges.get(0).getMin();
590 Long max = (Long) ranges.get(ranges.size() - 1).getMax();
591 return new Long[] { min, max };
592 } else if (targetBaseType instanceof DecimalTypeDefinition) {
593 DecimalTypeDefinition dtd = (DecimalTypeDefinition) targetBaseType;
594 List<RangeConstraint> ranges = dtd.getRangeStatements();
595 Long min = (Long) ranges.get(0).getMin();
596 Long max = (Long) ranges.get(ranges.size() - 1).getMax();
597 return new Long[] { min, max };
599 return parseRangeConstraint(typeToResolve,
600 findTypeDefinitionBuilder(modules, typeToResolve, builder),
605 private Date createEpochTime() {
606 Calendar c = Calendar.getInstance();
607 c.setTimeInMillis(0);
611 private static class SchemaContextImpl implements SchemaContext {
612 private final Set<Module> modules;
614 private SchemaContextImpl(Set<Module> modules) {
615 this.modules = modules;
619 public Set<DataSchemaNode> getDataDefinitions() {
620 final Set<DataSchemaNode> dataDefs = new HashSet<DataSchemaNode>();
621 for (Module m : modules) {
622 dataDefs.addAll(m.getChildNodes());
628 public Set<Module> getModules() {
633 public Set<NotificationDefinition> getNotifications() {
634 final Set<NotificationDefinition> notifications = new HashSet<NotificationDefinition>();
635 for (Module m : modules) {
636 notifications.addAll(m.getNotifications());
638 return notifications;
642 public Set<RpcDefinition> getOperations() {
643 final Set<RpcDefinition> rpcs = new HashSet<RpcDefinition>();
644 for (Module m : modules) {
645 rpcs.addAll(m.getRpcs());
651 public Set<ExtensionDefinition> getExtensions() {
652 final Set<ExtensionDefinition> extensions = new HashSet<ExtensionDefinition>();
653 for (Module m : modules) {
654 extensions.addAll(m.getExtensionSchemaNodes());
660 private static class TypeConstraints {
661 private final List<RangeConstraint> ranges = new ArrayList<RangeConstraint>();
662 private final List<LengthConstraint> lengths = new ArrayList<LengthConstraint>();
663 private final List<PatternConstraint> patterns = new ArrayList<PatternConstraint>();
664 private Integer fractionDigits;
666 public List<RangeConstraint> getRanges() {
670 public void addRanges(List<RangeConstraint> ranges) {
671 this.ranges.addAll(0, ranges);
674 public List<LengthConstraint> getLengths() {
678 public void addLengths(List<LengthConstraint> lengths) {
679 this.lengths.addAll(0, lengths);
682 public List<PatternConstraint> getPatterns() {
686 public void addPatterns(List<PatternConstraint> patterns) {
687 this.patterns.addAll(0, patterns);
690 public Integer getFractionDigits() {
691 return fractionDigits;
694 public void setFractionDigits(Integer fractionDigits) {
695 if (fractionDigits != null) {
696 this.fractionDigits = fractionDigits;