2 * Copyright (c) 2015 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.parser.stmt.rfc6020;
10 import static org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils.firstAttributeOf;
12 import com.google.common.base.CharMatcher;
13 import com.google.common.base.Preconditions;
14 import com.google.common.base.Splitter;
15 import com.google.common.base.Strings;
16 import com.google.common.collect.ImmutableList;
17 import com.google.common.collect.ImmutableMap;
18 import com.google.common.collect.ImmutableMap.Builder;
19 import com.google.common.collect.ImmutableSet;
20 import com.google.common.collect.Iterables;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.Date;
25 import java.util.HashSet;
26 import java.util.List;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31 import java.util.regex.PatternSyntaxException;
32 import javax.annotation.Nullable;
33 import javax.xml.xpath.XPath;
34 import javax.xml.xpath.XPathExpressionException;
35 import javax.xml.xpath.XPathFactory;
36 import org.antlr.v4.runtime.tree.TerminalNode;
37 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser;
38 import org.opendaylight.yangtools.yang.common.QName;
39 import org.opendaylight.yangtools.yang.common.QNameModule;
40 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
41 import org.opendaylight.yangtools.yang.model.api.Deviation;
42 import org.opendaylight.yangtools.yang.model.api.Deviation.Deviate;
43 import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier;
44 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
45 import org.opendaylight.yangtools.yang.model.api.Rfc6020Mapping;
46 import org.opendaylight.yangtools.yang.model.api.Status;
47 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
48 import org.opendaylight.yangtools.yang.model.api.stmt.BelongsToStatement;
49 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement;
50 import org.opendaylight.yangtools.yang.model.api.stmt.RevisionStatement;
51 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
52 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Relative;
53 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleStatement;
54 import org.opendaylight.yangtools.yang.model.util.RevisionAwareXPathImpl;
55 import org.opendaylight.yangtools.yang.parser.spi.meta.QNameCacheNamespace;
56 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
57 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
58 import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToPrefixToModuleName;
59 import org.opendaylight.yangtools.yang.parser.spi.source.ImpPrefixToModuleIdentifier;
60 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
61 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleIdentifierToModuleQName;
62 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleNameToModuleQName;
63 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
64 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
65 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
66 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
70 public final class Utils {
71 private static final int UNICODE_SCRIPT_FIX_COUNTER = 30;
72 private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
73 private static final CharMatcher DOUBLE_QUOTE_MATCHER = CharMatcher.is('"');
74 private static final CharMatcher SINGLE_QUOTE_MATCHER = CharMatcher.is('\'');
75 private static final CharMatcher LEFT_PARENTHESIS_MATCHER = CharMatcher.is('(');
76 private static final CharMatcher RIGHT_PARENTHESIS_MATCHER = CharMatcher.is(')');
77 private static final CharMatcher AMPERSAND_MATCHER = CharMatcher.is('&');
78 private static final CharMatcher QUESTION_MARK_MATCHER = CharMatcher.is('?');
79 private static final Splitter SLASH_SPLITTER = Splitter.on('/').omitEmptyStrings().trimResults();
80 private static final Splitter SPACE_SPLITTER = Splitter.on(' ').omitEmptyStrings().trimResults();
81 private static final Splitter COLON_SPLITTER = Splitter.on(":").omitEmptyStrings().trimResults();
82 private static final Pattern PATH_ABS = Pattern.compile("/[^/].*");
83 private static final Pattern BETWEEN_CURLY_BRACES_PATTERN = Pattern.compile("\\{(.+?)\\}");
84 private static final Set<String> JAVA_UNICODE_BLOCKS = ImmutableSet.<String>builder()
86 .add("AlchemicalSymbols")
87 .add("AlphabeticPresentationForms")
88 .add("AncientGreekMusicalNotation")
89 .add("AncientGreekNumbers")
90 .add("AncientSymbols")
92 .add("ArabicPresentationForms-A")
93 .add("ArabicPresentationForms-B")
94 .add("ArabicSupplement")
100 .add("BamumSupplement")
104 .add("BlockElements")
106 .add("BopomofoExtended")
109 .add("BraillePatterns")
112 .add("ByzantineMusicalSymbols")
116 .add("CJKCompatibility")
117 .add("CJKCompatibilityForms")
118 .add("CJKCompatibilityIdeographs")
119 .add("CJKCompatibilityIdeographsSupplement")
120 .add("CJKRadicalsSupplement")
122 .add("CJKSymbolsandPunctuation")
123 .add("CJKUnifiedIdeographs")
124 .add("CJKUnifiedIdeographsExtensionA")
125 .add("CJKUnifiedIdeographsExtensionB")
126 .add("CJKUnifiedIdeographsExtensionC")
127 .add("CJKUnifiedIdeographsExtensionD")
128 .add("CombiningDiacriticalMarks")
129 .add("CombiningDiacriticalMarksSupplement")
130 .add("CombiningHalfMarks")
131 .add("CombiningDiacriticalMarksforSymbols")
132 .add("CommonIndicNumberForms")
133 .add("ControlPictures")
135 .add("CountingRodNumerals")
137 .add("CuneiformNumbersandPunctuation")
138 .add("CurrencySymbols")
139 .add("CypriotSyllabary")
141 .add("CyrillicExtended-A")
142 .add("CyrillicExtended-B")
143 .add("CyrillicSupplementary")
146 .add("DevanagariExtended")
149 .add("EgyptianHieroglyphs")
151 .add("EnclosedAlphanumericSupplement")
152 .add("EnclosedAlphanumerics")
153 .add("EnclosedCJKLettersandMonths")
154 .add("EnclosedIdeographicSupplement")
156 .add("EthiopicExtended")
157 .add("EthiopicExtended-A")
158 .add("EthiopicSupplement")
159 .add("GeneralPunctuation")
160 .add("GeometricShapes")
162 .add("GeorgianSupplement")
165 .add("GreekandCoptic")
166 .add("GreekExtended")
169 .add("HalfwidthandFullwidthForms")
170 .add("HangulCompatibilityJamo")
172 .add("HangulJamoExtended-A")
173 .add("HangulJamoExtended-B")
174 .add("HangulSyllables")
177 .add("HighPrivateUseSurrogates")
178 .add("HighSurrogates")
180 .add("IdeographicDescriptionCharacters")
181 .add("ImperialAramaic")
182 .add("InscriptionalPahlavi")
183 .add("InscriptionalParthian")
184 .add("IPAExtensions")
187 .add("KanaSupplement")
189 .add("Kangxi Radicals")
192 .add("KatakanaPhoneticExtensions")
198 .add("Latin-1Supplement")
199 .add("LatinExtended-A")
200 .add("LatinExtendedAdditional")
201 .add("LatinExtended-B")
202 .add("LatinExtended-C")
203 .add("LatinExtended-D")
205 .add("LetterlikeSymbols")
207 .add("LinearBIdeograms")
208 .add("LinearBSyllabary")
210 .add("LowSurrogates")
216 .add("MathematicalAlphanumericSymbols")
217 .add("MathematicalOperators")
219 .add("MiscellaneousMathematicalSymbols-A")
220 .add("MiscellaneousMathematicalSymbols-B")
221 .add("MiscellaneousSymbols")
222 .add("MiscellaneousSymbolsandArrows")
223 .add("MiscellaneousSymbolsAndPictographs")
224 .add("MiscellaneousTechnical")
225 .add("ModifierToneLetters")
227 .add("MusicalSymbols")
229 .add("MyanmarExtended-A")
237 .add("OldSouthArabian")
239 .add("OpticalCharacterRecognition")
245 .add("PhoneticExtensions")
246 .add("PhoneticExtensionsSupplement")
248 .add("PrivateUseArea")
250 .add("RumiNumeralSymbols")
256 .add("SmallFormVariants")
257 .add("SpacingModifierLetters")
260 .add("SuperscriptsandSubscripts")
261 .add("SupplementalArrows-A")
262 .add("SupplementalArrows-B")
263 .add("SupplementalMathematicalOperators")
264 .add("SupplementalPunctuation")
265 .add("SupplementaryPrivateUseArea-A")
266 .add("SupplementaryPrivateUseArea-B")
275 .add("TaiXuanJingSymbols")
282 .add("TransportAndMapSymbols")
284 .add("UnifiedCanadianAboriginalSyllabics")
285 .add("UnifiedCanadianAboriginalSyllabicsExtended")
287 .add("VariationSelectors")
288 .add("VariationSelectorsSupplement")
289 .add("VedicExtensions")
290 .add("VerticalForms")
293 .add("YijingHexagramSymbols").build();
295 private static final Map<String, Deviate> KEYWORD_TO_DEVIATE_MAP;
297 Builder<String, Deviate> keywordToDeviateMapBuilder = ImmutableMap.builder();
298 for (Deviate deviate : Deviation.Deviate.values()) {
299 keywordToDeviateMapBuilder.put(deviate.getKeyword(), deviate);
301 KEYWORD_TO_DEVIATE_MAP = keywordToDeviateMapBuilder.build();
304 private static final ThreadLocal<XPathFactory> XPATH_FACTORY = new ThreadLocal<XPathFactory>() {
306 protected XPathFactory initialValue() {
307 return XPathFactory.newInstance();
312 throw new UnsupportedOperationException();
316 * Cleanup any resources attached to the current thread. Threads interacting with this class can cause thread-local
317 * caches to them. Invoke this method if you want to detach those resources.
319 public static void detachFromCurrentThread() {
320 XPATH_FACTORY.remove();
323 public static Collection<SchemaNodeIdentifier.Relative> transformKeysStringToKeyNodes(final StmtContext<?, ?, ?> ctx,
324 final String value) {
325 List<String> keyTokens = SPACE_SPLITTER.splitToList(value);
327 // to detect if key contains duplicates
328 if ((new HashSet<>(keyTokens)).size() < keyTokens.size()) {
329 // FIXME: report all duplicate keys
330 throw new SourceException(ctx.getStatementSourceReference(), "Duplicate value in list key: %s", value);
333 Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
335 for (String keyToken : keyTokens) {
337 SchemaNodeIdentifier.Relative keyNode = (Relative) SchemaNodeIdentifier.Relative.create(false,
338 Utils.qNameFromArgument(ctx, keyToken));
339 keyNodes.add(keyNode);
345 static Collection<SchemaNodeIdentifier.Relative> parseUniqueConstraintArgument(final StmtContext<?, ?, ?> ctx,
346 final String argumentValue) {
347 final Set<SchemaNodeIdentifier.Relative> uniqueConstraintNodes = new HashSet<>();
348 for (String uniqueArgToken : SPACE_SPLITTER.split(argumentValue)) {
349 final SchemaNodeIdentifier nodeIdentifier = Utils.nodeIdentifierFromPath(ctx, uniqueArgToken);
350 SourceException.throwIf(nodeIdentifier.isAbsolute(), ctx.getStatementSourceReference(),
351 "Unique statement argument '%s' contains schema node identifier '%s' "
352 + "which is not in the descendant node identifier form.", argumentValue, uniqueArgToken);
353 uniqueConstraintNodes.add((SchemaNodeIdentifier.Relative) nodeIdentifier);
355 return ImmutableSet.copyOf(uniqueConstraintNodes);
358 private static String trimSingleLastSlashFromXPath(final String path) {
359 return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
362 static RevisionAwareXPath parseXPath(final StmtContext<?, ?, ?> ctx, final String path) {
363 final XPath xPath = XPATH_FACTORY.get().newXPath();
364 xPath.setNamespaceContext(StmtNamespaceContext.create(ctx));
366 final String trimmed = trimSingleLastSlashFromXPath(path);
368 // TODO: we could capture the result and expose its 'evaluate' method
369 xPath.compile(trimmed);
370 } catch (XPathExpressionException e) {
371 LOG.warn("Argument \"{}\" is not valid XPath string at \"{}\"", path, ctx.getStatementSourceReference(), e);
374 return new RevisionAwareXPathImpl(path, PATH_ABS.matcher(path).matches());
377 public static QName trimPrefix(final QName identifier) {
378 String prefixedLocalName = identifier.getLocalName();
379 String[] namesParts = prefixedLocalName.split(":");
381 if (namesParts.length == 2) {
382 String localName = namesParts[1];
383 return QName.create(identifier.getModule(), localName);
389 public static String trimPrefix(final String identifier) {
390 List<String> namesParts = COLON_SPLITTER.splitToList(identifier);
391 if (namesParts.size() == 2) {
392 return namesParts.get(1);
399 * Based on identifier read from source and collections of relevant prefixes and statement definitions mappings
400 * provided for actual phase, method resolves and returns valid QName for declared statement to be written.
401 * This applies to any declared statement, including unknown statements.
403 * @param prefixes - collection of all relevant prefix mappings supplied for actual parsing phase
404 * @param stmtDef - collection of all relevant statement definition mappings provided for actual parsing phase
405 * @param identifier - statement to parse from source
406 * @return valid QName for declared statement to be written
409 public static QName getValidStatementDefinition(final PrefixToModule prefixes,
410 final QNameToStatementDefinition stmtDef, final QName identifier) {
411 if (stmtDef.get(identifier) != null) {
412 return stmtDef.get(identifier).getStatementName();
414 String prefixedLocalName = identifier.getLocalName();
415 String[] namesParts = prefixedLocalName.split(":");
417 if (namesParts.length == 2) {
418 String prefix = namesParts[0];
419 String localName = namesParts[1];
421 if (prefixes == null) {
425 QNameModule qNameModule = prefixes.get(prefix);
426 if (qNameModule == null) {
430 if (prefixes.isPreLinkageMap()) {
431 StatementDefinition foundStmtDef = stmtDef.getByNamespaceAndLocalName(qNameModule.getNamespace(),
433 return foundStmtDef != null ? foundStmtDef.getStatementName() : null;
435 QName qName = QName.create(qNameModule, localName);
436 return stmtDef.get(qName) != null ? qName : null;
443 static SchemaNodeIdentifier nodeIdentifierFromPath(final StmtContext<?, ?, ?> ctx, final String path) {
444 // FIXME: is the path trimming really necessary??
445 final List<QName> qNames = new ArrayList<>();
446 for (String nodeName : SLASH_SPLITTER.split(trimSingleLastSlashFromXPath(path))) {
448 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
450 } catch (Exception e) {
451 throw new IllegalArgumentException(
452 String.format("Failed to parse node '%s' in path '%s'", nodeName, path), e);
456 return SchemaNodeIdentifier.create(qNames, PATH_ABS.matcher(path).matches());
459 public static String stringFromStringContext(final YangStatementParser.ArgumentContext context) {
460 StringBuilder sb = new StringBuilder();
461 List<TerminalNode> strings = context.STRING();
462 if (strings.isEmpty()) {
463 strings = Collections.singletonList(context.IDENTIFIER());
465 for (TerminalNode stringNode : strings) {
466 final String str = stringNode.getText();
467 char firstChar = str.charAt(0);
468 final CharMatcher quoteMatcher;
469 if (SINGLE_QUOTE_MATCHER.matches(firstChar)) {
470 quoteMatcher = SINGLE_QUOTE_MATCHER;
471 } else if (DOUBLE_QUOTE_MATCHER.matches(firstChar)) {
472 quoteMatcher = DOUBLE_QUOTE_MATCHER;
477 sb.append(quoteMatcher.removeFrom(str.substring(1, str.length() - 1)));
479 return sb.toString();
482 public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
483 if (Strings.isNullOrEmpty(value)) {
484 return ctx.getPublicDefinition().getStatementName();
488 QNameModule qNameModule = null;
489 String localName = null;
491 String[] namesParts = value.split(":");
492 switch (namesParts.length) {
494 localName = namesParts[0];
495 qNameModule = getRootModuleQName(ctx);
498 prefix = namesParts[0];
499 localName = namesParts[1];
500 qNameModule = getModuleQNameByPrefix(ctx, prefix);
501 // in case of unknown statement argument, we're not going to parse it
502 if (qNameModule == null
503 && ctx.getPublicDefinition().getDeclaredRepresentationClass()
504 .isAssignableFrom(UnknownStatementImpl.class)) {
506 qNameModule = getRootModuleQName(ctx);
508 if (qNameModule == null
509 && Iterables.getLast(ctx.getCopyHistory()) == StmtContext.TypeOfCopy.ADDED_BY_AUGMENTATION) {
510 ctx = ctx.getOriginalCtx();
511 qNameModule = getModuleQNameByPrefix(ctx, prefix);
516 Preconditions.checkArgument(qNameModule != null,
517 "Error in module '%s': can not resolve QNameModule for '%s'. Statement source at %s",
518 ctx.getRoot().rawStatementArgument(), value, ctx.getStatementSourceReference());
519 final QNameModule resultQNameModule;
520 if (qNameModule.getRevision() == null) {
521 resultQNameModule = QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV)
524 resultQNameModule = qNameModule;
527 return ctx.getFromNamespace(QNameCacheNamespace.class, QName.create(resultQNameModule, localName));
530 public static QNameModule getModuleQNameByPrefix(final StmtContext<?, ?, ?> ctx, final String prefix) {
531 final ModuleIdentifier modId = ctx.getRoot().getFromNamespace(ImpPrefixToModuleIdentifier.class, prefix);
532 final QNameModule qNameModule = ctx.getFromNamespace(ModuleIdentifierToModuleQName.class, modId);
534 if (qNameModule == null && StmtContextUtils.producesDeclared(ctx.getRoot(), SubmoduleStatement.class)) {
535 String moduleName = ctx.getRoot().getFromNamespace(BelongsToPrefixToModuleName.class, prefix);
536 return ctx.getFromNamespace(ModuleNameToModuleQName.class, moduleName);
541 public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
546 final StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
547 final QNameModule qNameModule;
549 if (StmtContextUtils.producesDeclared(rootCtx, ModuleStatement.class)) {
550 qNameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
551 } else if (StmtContextUtils.producesDeclared(rootCtx, SubmoduleStatement.class)) {
552 final String belongsToModuleName = firstAttributeOf(rootCtx.substatements(), BelongsToStatement.class);
553 qNameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
558 Preconditions.checkArgument(qNameModule != null, "Failed to look up root QNameModule for %s", ctx);
559 if (qNameModule.getRevision() != null) {
563 return QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV).intern();
567 public static StatementContextBase<?, ?, ?> findNode(final StmtContext<?, ?, ?> rootStmtCtx,
568 final SchemaNodeIdentifier node) {
569 return (StatementContextBase<?, ?, ?>) rootStmtCtx.getFromNamespace(SchemaNodeIdentifierBuildNamespace.class, node);
572 public static boolean isUnknownNode(final StmtContext<?, ?, ?> stmtCtx) {
573 return stmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
574 .isAssignableFrom(UnknownStatementImpl.class);
577 public static Deviation.Deviate parseDeviateFromString(final StmtContext<?, ?, ?> ctx, final String deviateKeyword) {
578 return Preconditions.checkNotNull(KEYWORD_TO_DEVIATE_MAP.get(deviateKeyword),
579 "String '%s' is not valid deviate argument. Statement source at %s", deviateKeyword,
580 ctx.getStatementSourceReference());
583 public static Status parseStatus(final String value) {
586 return Status.CURRENT;
588 return Status.DEPRECATED;
590 return Status.OBSOLETE;
592 LOG.warn("Invalid 'status' statement: {}", value);
597 public static Date getLatestRevision(final Iterable<? extends StmtContext<?, ?, ?>> subStmts) {
598 Date revision = null;
599 for (StmtContext<?, ?, ?> subStmt : subStmts) {
600 if (subStmt.getPublicDefinition().getDeclaredRepresentationClass().isAssignableFrom(RevisionStatement
602 if (revision == null && subStmt.getStatementArgument() != null) {
603 revision = (Date) subStmt.getStatementArgument();
604 } else if (subStmt.getStatementArgument() != null && ((Date) subStmt.getStatementArgument()).compareTo
606 revision = (Date) subStmt.getStatementArgument();
613 public static boolean isModuleIdentifierWithoutSpecifiedRevision(final Object o) {
614 return (o instanceof ModuleIdentifier)
615 && (((ModuleIdentifier) o).getRevision() == SimpleDateFormatUtil.DEFAULT_DATE_IMP || ((ModuleIdentifier) o)
616 .getRevision() == SimpleDateFormatUtil.DEFAULT_BELONGS_TO_DATE);
620 * Replaces illegal characters of QName by the name of the character (e.g.
621 * '?' is replaced by "QuestionMark" etc.).
625 * @return result String
627 public static String replaceIllegalCharsForQName(String string) {
628 string = LEFT_PARENTHESIS_MATCHER.replaceFrom(string, "LeftParenthesis");
629 string = RIGHT_PARENTHESIS_MATCHER.replaceFrom(string, "RightParenthesis");
630 string = AMPERSAND_MATCHER.replaceFrom(string, "Ampersand");
631 string = QUESTION_MARK_MATCHER.replaceFrom(string, "QuestionMark");
636 public static String fixUnicodeScriptPattern(String rawPattern) {
637 for (int i = 0; i < UNICODE_SCRIPT_FIX_COUNTER; i++) {
639 Pattern.compile(rawPattern);
641 } catch(PatternSyntaxException ex) {
642 LOG.debug("Invalid regex pattern syntax in: {}", rawPattern, ex);
643 if (ex.getMessage().contains("Unknown character script name")) {
644 rawPattern = fixUnknownScripts(ex.getMessage(), rawPattern);
651 LOG.warn("Regex pattern could not be fixed: {}", rawPattern);
655 private static String fixUnknownScripts(final String exMessage, final String rawPattern) {
656 StringBuilder result = new StringBuilder(rawPattern);
657 Matcher matcher = BETWEEN_CURLY_BRACES_PATTERN.matcher(exMessage);
658 if (matcher.find()) {
659 String capturedGroup = matcher.group(1);
660 if (JAVA_UNICODE_BLOCKS.contains(capturedGroup)) {
661 int idx = rawPattern.indexOf("Is" + capturedGroup);
662 result = result.replace(idx, idx + 2, "In");
665 return result.toString();
668 public static boolean belongsToTheSameModule(QName targetStmtQName, QName sourceStmtQName) {
669 if (targetStmtQName.getModule().equals(sourceStmtQName.getModule())) {
675 public static boolean isPresenceContainer(StatementContextBase<?, ?, ?> targetCtx) {
676 if (!targetCtx.getPublicDefinition().equals(Rfc6020Mapping.CONTAINER)) {
680 final List<StatementContextBase<?, ?, ?>> targetSubStatements = new ImmutableList.Builder<StatementContextBase<?, ?, ?>>()
681 .addAll(targetCtx.declaredSubstatements()).addAll(targetCtx.effectiveSubstatements()).build();
682 for (final StatementContextBase<?, ?, ?> subStatement : targetSubStatements) {
683 if (subStatement.getPublicDefinition().equals(Rfc6020Mapping.PRESENCE)) {