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 Pattern PATH_ABS = Pattern.compile("/[^/].*");
82 private static final Pattern BETWEEN_CURLY_BRACES_PATTERN = Pattern.compile("\\{(.+?)\\}");
83 private static final Set<String> JAVA_UNICODE_BLOCKS = ImmutableSet.<String>builder()
85 .add("AlchemicalSymbols")
86 .add("AlphabeticPresentationForms")
87 .add("AncientGreekMusicalNotation")
88 .add("AncientGreekNumbers")
89 .add("AncientSymbols")
91 .add("ArabicPresentationForms-A")
92 .add("ArabicPresentationForms-B")
93 .add("ArabicSupplement")
99 .add("BamumSupplement")
103 .add("BlockElements")
105 .add("BopomofoExtended")
108 .add("BraillePatterns")
111 .add("ByzantineMusicalSymbols")
115 .add("CJKCompatibility")
116 .add("CJKCompatibilityForms")
117 .add("CJKCompatibilityIdeographs")
118 .add("CJKCompatibilityIdeographsSupplement")
119 .add("CJKRadicalsSupplement")
121 .add("CJKSymbolsandPunctuation")
122 .add("CJKUnifiedIdeographs")
123 .add("CJKUnifiedIdeographsExtensionA")
124 .add("CJKUnifiedIdeographsExtensionB")
125 .add("CJKUnifiedIdeographsExtensionC")
126 .add("CJKUnifiedIdeographsExtensionD")
127 .add("CombiningDiacriticalMarks")
128 .add("CombiningDiacriticalMarksSupplement")
129 .add("CombiningHalfMarks")
130 .add("CombiningDiacriticalMarksforSymbols")
131 .add("CommonIndicNumberForms")
132 .add("ControlPictures")
134 .add("CountingRodNumerals")
136 .add("CuneiformNumbersandPunctuation")
137 .add("CurrencySymbols")
138 .add("CypriotSyllabary")
140 .add("CyrillicExtended-A")
141 .add("CyrillicExtended-B")
142 .add("CyrillicSupplementary")
145 .add("DevanagariExtended")
148 .add("EgyptianHieroglyphs")
150 .add("EnclosedAlphanumericSupplement")
151 .add("EnclosedAlphanumerics")
152 .add("EnclosedCJKLettersandMonths")
153 .add("EnclosedIdeographicSupplement")
155 .add("EthiopicExtended")
156 .add("EthiopicExtended-A")
157 .add("EthiopicSupplement")
158 .add("GeneralPunctuation")
159 .add("GeometricShapes")
161 .add("GeorgianSupplement")
164 .add("GreekandCoptic")
165 .add("GreekExtended")
168 .add("HalfwidthandFullwidthForms")
169 .add("HangulCompatibilityJamo")
171 .add("HangulJamoExtended-A")
172 .add("HangulJamoExtended-B")
173 .add("HangulSyllables")
176 .add("HighPrivateUseSurrogates")
177 .add("HighSurrogates")
179 .add("IdeographicDescriptionCharacters")
180 .add("ImperialAramaic")
181 .add("InscriptionalPahlavi")
182 .add("InscriptionalParthian")
183 .add("IPAExtensions")
186 .add("KanaSupplement")
188 .add("Kangxi Radicals")
191 .add("KatakanaPhoneticExtensions")
197 .add("Latin-1Supplement")
198 .add("LatinExtended-A")
199 .add("LatinExtendedAdditional")
200 .add("LatinExtended-B")
201 .add("LatinExtended-C")
202 .add("LatinExtended-D")
204 .add("LetterlikeSymbols")
206 .add("LinearBIdeograms")
207 .add("LinearBSyllabary")
209 .add("LowSurrogates")
215 .add("MathematicalAlphanumericSymbols")
216 .add("MathematicalOperators")
218 .add("MiscellaneousMathematicalSymbols-A")
219 .add("MiscellaneousMathematicalSymbols-B")
220 .add("MiscellaneousSymbols")
221 .add("MiscellaneousSymbolsandArrows")
222 .add("MiscellaneousSymbolsAndPictographs")
223 .add("MiscellaneousTechnical")
224 .add("ModifierToneLetters")
226 .add("MusicalSymbols")
228 .add("MyanmarExtended-A")
236 .add("OldSouthArabian")
238 .add("OpticalCharacterRecognition")
244 .add("PhoneticExtensions")
245 .add("PhoneticExtensionsSupplement")
247 .add("PrivateUseArea")
249 .add("RumiNumeralSymbols")
255 .add("SmallFormVariants")
256 .add("SpacingModifierLetters")
259 .add("SuperscriptsandSubscripts")
260 .add("SupplementalArrows-A")
261 .add("SupplementalArrows-B")
262 .add("SupplementalMathematicalOperators")
263 .add("SupplementalPunctuation")
264 .add("SupplementaryPrivateUseArea-A")
265 .add("SupplementaryPrivateUseArea-B")
274 .add("TaiXuanJingSymbols")
281 .add("TransportAndMapSymbols")
283 .add("UnifiedCanadianAboriginalSyllabics")
284 .add("UnifiedCanadianAboriginalSyllabicsExtended")
286 .add("VariationSelectors")
287 .add("VariationSelectorsSupplement")
288 .add("VedicExtensions")
289 .add("VerticalForms")
292 .add("YijingHexagramSymbols").build();
294 private static final Map<String, Deviate> KEYWORD_TO_DEVIATE_MAP;
296 Builder<String, Deviate> keywordToDeviateMapBuilder = ImmutableMap.builder();
297 for (Deviate deviate : Deviation.Deviate.values()) {
298 keywordToDeviateMapBuilder.put(deviate.getKeyword(), deviate);
300 KEYWORD_TO_DEVIATE_MAP = keywordToDeviateMapBuilder.build();
303 private static final ThreadLocal<XPathFactory> XPATH_FACTORY = new ThreadLocal<XPathFactory>() {
305 protected XPathFactory initialValue() {
306 return XPathFactory.newInstance();
311 throw new UnsupportedOperationException();
315 * Cleanup any resources attached to the current thread. Threads interacting with this class can cause thread-local
316 * caches to them. Invoke this method if you want to detach those resources.
318 public static void detachFromCurrentThread() {
319 XPATH_FACTORY.remove();
322 public static Collection<SchemaNodeIdentifier.Relative> transformKeysStringToKeyNodes(final StmtContext<?, ?, ?> ctx,
323 final String value) {
324 List<String> keyTokens = SPACE_SPLITTER.splitToList(value);
326 // to detect if key contains duplicates
327 if ((new HashSet<>(keyTokens)).size() < keyTokens.size()) {
328 // FIXME: report all duplicate keys
329 throw new SourceException(ctx.getStatementSourceReference(), "Duplicate value in list key: %s", value);
332 Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
334 for (String keyToken : keyTokens) {
336 SchemaNodeIdentifier.Relative keyNode = (Relative) SchemaNodeIdentifier.Relative.create(false,
337 Utils.qNameFromArgument(ctx, keyToken));
338 keyNodes.add(keyNode);
344 static Collection<SchemaNodeIdentifier.Relative> parseUniqueConstraintArgument(final StmtContext<?, ?, ?> ctx,
345 final String argumentValue) {
346 final Set<SchemaNodeIdentifier.Relative> uniqueConstraintNodes = new HashSet<>();
347 for (String uniqueArgToken : SPACE_SPLITTER.split(argumentValue)) {
348 final SchemaNodeIdentifier nodeIdentifier = Utils.nodeIdentifierFromPath(ctx, uniqueArgToken);
349 SourceException.throwIf(nodeIdentifier.isAbsolute(), ctx.getStatementSourceReference(),
350 "Unique statement argument '%s' contains schema node identifier '%s' "
351 + "which is not in the descendant node identifier form.", argumentValue, uniqueArgToken);
352 uniqueConstraintNodes.add((SchemaNodeIdentifier.Relative) nodeIdentifier);
354 return ImmutableSet.copyOf(uniqueConstraintNodes);
357 private static String trimSingleLastSlashFromXPath(final String path) {
358 return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
361 static RevisionAwareXPath parseXPath(final StmtContext<?, ?, ?> ctx, final String path) {
362 final XPath xPath = XPATH_FACTORY.get().newXPath();
363 xPath.setNamespaceContext(StmtNamespaceContext.create(ctx));
365 final String trimmed = trimSingleLastSlashFromXPath(path);
367 // TODO: we could capture the result and expose its 'evaluate' method
368 xPath.compile(trimmed);
369 } catch (XPathExpressionException e) {
370 LOG.warn("Argument \"{}\" is not valid XPath string at \"{}\"", path, ctx.getStatementSourceReference(), e);
373 return new RevisionAwareXPathImpl(path, PATH_ABS.matcher(path).matches());
376 public static QName trimPrefix(final QName identifier) {
377 String prefixedLocalName = identifier.getLocalName();
378 String[] namesParts = prefixedLocalName.split(":");
380 if (namesParts.length == 2) {
381 String localName = namesParts[1];
382 return QName.create(identifier.getModule(), localName);
390 * Based on identifier read from source and collections of relevant prefixes and statement definitions mappings
391 * provided for actual phase, method resolves and returns valid QName for declared statement to be written.
392 * This applies to any declared statement, including unknown statements.
394 * @param prefixes - collection of all relevant prefix mappings supplied for actual parsing phase
395 * @param stmtDef - collection of all relevant statement definition mappings provided for actual parsing phase
396 * @param identifier - statement to parse from source
397 * @return valid QName for declared statement to be written
400 public static QName getValidStatementDefinition(final PrefixToModule prefixes,
401 final QNameToStatementDefinition stmtDef, final QName identifier) {
402 if (stmtDef.get(identifier) != null) {
403 return stmtDef.get(identifier).getStatementName();
405 String prefixedLocalName = identifier.getLocalName();
406 String[] namesParts = prefixedLocalName.split(":");
408 if (namesParts.length == 2) {
409 String prefix = namesParts[0];
410 String localName = namesParts[1];
412 if (prefixes == null) {
416 QNameModule qNameModule = prefixes.get(prefix);
417 if (qNameModule == null) {
421 if (prefixes.isPreLinkageMap()) {
422 StatementDefinition foundStmtDef = stmtDef.getByNamespaceAndLocalName(qNameModule.getNamespace(),
424 return foundStmtDef != null ? foundStmtDef.getStatementName() : null;
426 QName qName = QName.create(qNameModule, localName);
427 return stmtDef.get(qName) != null ? qName : null;
434 static SchemaNodeIdentifier nodeIdentifierFromPath(final StmtContext<?, ?, ?> ctx, final String path) {
435 // FIXME: is the path trimming really necessary??
436 final List<QName> qNames = new ArrayList<>();
437 for (String nodeName : SLASH_SPLITTER.split(trimSingleLastSlashFromXPath(path))) {
439 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
441 } catch (Exception e) {
442 throw new IllegalArgumentException(
443 String.format("Failed to parse node '%s' in path '%s'", nodeName, path), e);
447 return SchemaNodeIdentifier.create(qNames, PATH_ABS.matcher(path).matches());
450 public static String stringFromStringContext(final YangStatementParser.ArgumentContext context) {
451 StringBuilder sb = new StringBuilder();
452 List<TerminalNode> strings = context.STRING();
453 if (strings.isEmpty()) {
454 strings = Collections.singletonList(context.IDENTIFIER());
456 for (TerminalNode stringNode : strings) {
457 final String str = stringNode.getText();
458 char firstChar = str.charAt(0);
459 final CharMatcher quoteMatcher;
460 if (SINGLE_QUOTE_MATCHER.matches(firstChar)) {
461 quoteMatcher = SINGLE_QUOTE_MATCHER;
462 } else if (DOUBLE_QUOTE_MATCHER.matches(firstChar)) {
463 quoteMatcher = DOUBLE_QUOTE_MATCHER;
468 sb.append(quoteMatcher.removeFrom(str.substring(1, str.length() - 1)));
470 return sb.toString();
473 public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
474 if (Strings.isNullOrEmpty(value)) {
475 return ctx.getPublicDefinition().getStatementName();
479 QNameModule qNameModule = null;
480 String localName = null;
482 String[] namesParts = value.split(":");
483 switch (namesParts.length) {
485 localName = namesParts[0];
486 qNameModule = getRootModuleQName(ctx);
489 prefix = namesParts[0];
490 localName = namesParts[1];
491 qNameModule = getModuleQNameByPrefix(ctx, prefix);
492 // in case of unknown statement argument, we're not going to parse it
493 if (qNameModule == null
494 && ctx.getPublicDefinition().getDeclaredRepresentationClass()
495 .isAssignableFrom(UnknownStatementImpl.class)) {
497 qNameModule = getRootModuleQName(ctx);
499 if (qNameModule == null
500 && Iterables.getLast(ctx.getCopyHistory()) == StmtContext.TypeOfCopy.ADDED_BY_AUGMENTATION) {
501 ctx = ctx.getOriginalCtx();
502 qNameModule = getModuleQNameByPrefix(ctx, prefix);
507 Preconditions.checkArgument(qNameModule != null,
508 "Error in module '%s': can not resolve QNameModule for '%s'. Statement source at %s",
509 ctx.getRoot().rawStatementArgument(), value, ctx.getStatementSourceReference());
510 final QNameModule resultQNameModule;
511 if (qNameModule.getRevision() == null) {
512 resultQNameModule = QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV)
515 resultQNameModule = qNameModule;
518 return ctx.getFromNamespace(QNameCacheNamespace.class, QName.create(resultQNameModule, localName));
521 public static QNameModule getModuleQNameByPrefix(final StmtContext<?, ?, ?> ctx, final String prefix) {
522 final ModuleIdentifier modId = ctx.getRoot().getFromNamespace(ImpPrefixToModuleIdentifier.class, prefix);
523 final QNameModule qNameModule = ctx.getFromNamespace(ModuleIdentifierToModuleQName.class, modId);
525 if (qNameModule == null && StmtContextUtils.producesDeclared(ctx.getRoot(), SubmoduleStatement.class)) {
526 String moduleName = ctx.getRoot().getFromNamespace(BelongsToPrefixToModuleName.class, prefix);
527 return ctx.getFromNamespace(ModuleNameToModuleQName.class, moduleName);
532 public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
537 final StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
538 final QNameModule qNameModule;
540 if (StmtContextUtils.producesDeclared(rootCtx, ModuleStatement.class)) {
541 qNameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
542 } else if (StmtContextUtils.producesDeclared(rootCtx, SubmoduleStatement.class)) {
543 final String belongsToModuleName = firstAttributeOf(rootCtx.substatements(), BelongsToStatement.class);
544 qNameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
549 Preconditions.checkArgument(qNameModule != null, "Failed to look up root QNameModule for %s", ctx);
550 if (qNameModule.getRevision() != null) {
554 return QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV).intern();
558 public static StatementContextBase<?, ?, ?> findNode(final StmtContext<?, ?, ?> rootStmtCtx,
559 final SchemaNodeIdentifier node) {
560 return (StatementContextBase<?, ?, ?>) rootStmtCtx.getFromNamespace(SchemaNodeIdentifierBuildNamespace.class, node);
563 public static boolean isUnknownNode(final StmtContext<?, ?, ?> stmtCtx) {
564 return stmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
565 .isAssignableFrom(UnknownStatementImpl.class);
568 public static Deviation.Deviate parseDeviateFromString(final StmtContext<?, ?, ?> ctx, final String deviateKeyword) {
569 return Preconditions.checkNotNull(KEYWORD_TO_DEVIATE_MAP.get(deviateKeyword),
570 "String '%s' is not valid deviate argument. Statement source at %s", deviateKeyword,
571 ctx.getStatementSourceReference());
574 public static Status parseStatus(final String value) {
577 return Status.CURRENT;
579 return Status.DEPRECATED;
581 return Status.OBSOLETE;
583 LOG.warn("Invalid 'status' statement: {}", value);
588 public static Date getLatestRevision(final Iterable<? extends StmtContext<?, ?, ?>> subStmts) {
589 Date revision = null;
590 for (StmtContext<?, ?, ?> subStmt : subStmts) {
591 if (subStmt.getPublicDefinition().getDeclaredRepresentationClass().isAssignableFrom(RevisionStatement
593 if (revision == null && subStmt.getStatementArgument() != null) {
594 revision = (Date) subStmt.getStatementArgument();
595 } else if (subStmt.getStatementArgument() != null && ((Date) subStmt.getStatementArgument()).compareTo
597 revision = (Date) subStmt.getStatementArgument();
604 public static boolean isModuleIdentifierWithoutSpecifiedRevision(final Object o) {
605 return (o instanceof ModuleIdentifier)
606 && (((ModuleIdentifier) o).getRevision() == SimpleDateFormatUtil.DEFAULT_DATE_IMP || ((ModuleIdentifier) o)
607 .getRevision() == SimpleDateFormatUtil.DEFAULT_BELONGS_TO_DATE);
611 * Replaces illegal characters of QName by the name of the character (e.g.
612 * '?' is replaced by "QuestionMark" etc.).
616 * @return result String
618 public static String replaceIllegalCharsForQName(String string) {
619 string = LEFT_PARENTHESIS_MATCHER.replaceFrom(string, "LeftParenthesis");
620 string = RIGHT_PARENTHESIS_MATCHER.replaceFrom(string, "RightParenthesis");
621 string = AMPERSAND_MATCHER.replaceFrom(string, "Ampersand");
622 string = QUESTION_MARK_MATCHER.replaceFrom(string, "QuestionMark");
627 public static String fixUnicodeScriptPattern(String rawPattern) {
628 for (int i = 0; i < UNICODE_SCRIPT_FIX_COUNTER; i++) {
630 Pattern.compile(rawPattern);
632 } catch(PatternSyntaxException ex) {
633 LOG.debug("Invalid regex pattern syntax in: {}", rawPattern, ex);
634 if (ex.getMessage().contains("Unknown character script name")) {
635 rawPattern = fixUnknownScripts(ex.getMessage(), rawPattern);
642 LOG.warn("Regex pattern could not be fixed: {}", rawPattern);
646 private static String fixUnknownScripts(final String exMessage, final String rawPattern) {
647 StringBuilder result = new StringBuilder(rawPattern);
648 Matcher matcher = BETWEEN_CURLY_BRACES_PATTERN.matcher(exMessage);
649 if (matcher.find()) {
650 String capturedGroup = matcher.group(1);
651 if (JAVA_UNICODE_BLOCKS.contains(capturedGroup)) {
652 int idx = rawPattern.indexOf("Is" + capturedGroup);
653 result = result.replace(idx, idx + 2, "In");
656 return result.toString();
659 public static boolean belongsToTheSameModule(QName targetStmtQName, QName sourceStmtQName) {
660 if (targetStmtQName.getModule().equals(sourceStmtQName.getModule())) {
666 public static boolean isPresenceContainer(StatementContextBase<?, ?, ?> targetCtx) {
667 if (!targetCtx.getPublicDefinition().equals(Rfc6020Mapping.CONTAINER)) {
671 final List<StatementContextBase<?, ?, ?>> targetSubStatements = new ImmutableList.Builder<StatementContextBase<?, ?, ?>>()
672 .addAll(targetCtx.declaredSubstatements()).addAll(targetCtx.effectiveSubstatements()).build();
673 for (final StatementContextBase<?, ?, ?> subStatement : targetSubStatements) {
674 if (subStatement.getPublicDefinition().equals(Rfc6020Mapping.PRESENCE)) {