Bug 6551: Support for third-party Yang extensions implementation 26/45226/7
authorPeter Kajsa <pkajsa@cisco.com>
Tue, 6 Sep 2016 12:35:02 +0000 (14:35 +0200)
committerRobert Varga <nite@hq.sk>
Thu, 22 Sep 2016 11:54:00 +0000 (11:54 +0000)
Minor changes in yang statement parser in order to allow implementation
of custom inference pipeline.
- implementation of CustomStatementParserBuilder, which provides
construction of custom statement parser in user-friendly way.
- example and unit test of third-party extension plugin.

Change-Id: I0cdf0e28bd69af4cb41328be6e6a647df58f4fd9
Signed-off-by: Peter Kajsa <pkajsa@cisco.com>
14 files changed:
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StatementSupportBundle.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/spi/validation/ValidationBundlesNamespace.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/CustomStatementParserBuilder.java [new file with mode: 0644]
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/ChildSchemaNodes.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/SchemaNodeIdentifierBuildNamespace.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/URIStringToImpPrefix.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/YangInferencePipeline.java
yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/CustomInferencePipeline.java [new file with mode: 0644]
yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyExtensionEffectiveStatementImpl.java [new file with mode: 0644]
yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyExtensionPluginTest.java [new file with mode: 0644]
yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyExtensionStatementImpl.java [new file with mode: 0644]
yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyExtensionsMapping.java [new file with mode: 0644]
yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyNamespace.java [new file with mode: 0644]
yang/yang-parser-impl/src/test/resources/plugin-test/foo.yang [new file with mode: 0644]

index 08e723ecce63c70dc872675997e61c765d4c2606..1f008e723d6cadc7cb060ec00079fde32d36b8dc 100644 (file)
@@ -23,9 +23,9 @@ public final class StatementSupportBundle implements Immutable,NamespaceBehaviou
     private final ImmutableMap<QName, StatementSupport<?,?,?>> definitions;
     private final ImmutableMap<Class<?>, NamespaceBehaviour<?, ?, ?>> namespaceDefinitions;
 
     private final ImmutableMap<QName, StatementSupport<?,?,?>> definitions;
     private final ImmutableMap<Class<?>, NamespaceBehaviour<?, ?, ?>> namespaceDefinitions;
 
-    private StatementSupportBundle(StatementSupportBundle parent,
-                                   ImmutableMap<QName, StatementSupport<?, ?, ?>> statements,
-                                   ImmutableMap<Class<?>, NamespaceBehaviour<?, ?, ?>> namespaces) {
+    private StatementSupportBundle(final StatementSupportBundle parent,
+                                   final ImmutableMap<QName, StatementSupport<?, ?, ?>> statements,
+                                   final ImmutableMap<Class<?>, NamespaceBehaviour<?, ?, ?>> namespaces) {
         this.parent = parent;
         this.definitions = statements;
         this.namespaceDefinitions = namespaces;
         this.parent = parent;
         this.definitions = statements;
         this.namespaceDefinitions = namespaces;
@@ -35,18 +35,22 @@ public final class StatementSupportBundle implements Immutable,NamespaceBehaviou
         return definitions;
     }
 
         return definitions;
     }
 
+    public ImmutableMap<Class<?>, NamespaceBehaviour<?, ?, ?>> getNamespaceDefinitions() {
+        return namespaceDefinitions;
+    }
+
     public static Builder builder() {
         return new Builder(EMPTY);
     }
 
     public static Builder builder() {
         return new Builder(EMPTY);
     }
 
-    public static Builder derivedFrom(StatementSupportBundle parent) {
+    public static Builder derivedFrom(final StatementSupportBundle parent) {
         return new Builder(parent);
     }
 
     @Override
         return new Builder(parent);
     }
 
     @Override
-    public <K, V, N extends IdentifierNamespace<K, V>> NamespaceBehaviour<K, V, N> getNamespaceBehaviour(Class<N> namespace)
+    public <K, V, N extends IdentifierNamespace<K, V>> NamespaceBehaviour<K, V, N> getNamespaceBehaviour(final Class<N> namespace)
             throws NamespaceNotAvailableException {
             throws NamespaceNotAvailableException {
-        NamespaceBehaviour<?, ?, ?> potential = namespaceDefinitions.get(namespace);
+        final NamespaceBehaviour<?, ?, ?> potential = namespaceDefinitions.get(namespace);
         if (potential != null) {
             Preconditions.checkState(namespace.equals(potential.getIdentifier()));
 
         if (potential != null) {
             Preconditions.checkState(namespace.equals(potential.getIdentifier()));
 
@@ -62,7 +66,7 @@ public final class StatementSupportBundle implements Immutable,NamespaceBehaviou
         return null;
     }
 
         return null;
     }
 
-    public <K, V, N extends IdentifierNamespace<K, V>> boolean hasNamespaceBehaviour(Class<N> namespace) {
+    public <K, V, N extends IdentifierNamespace<K, V>> boolean hasNamespaceBehaviour(final Class<N> namespace) {
         if (namespaceDefinitions.containsKey(namespace)) {
             return true;
         }
         if (namespaceDefinitions.containsKey(namespace)) {
             return true;
         }
@@ -72,8 +76,8 @@ public final class StatementSupportBundle implements Immutable,NamespaceBehaviou
         return false;
     }
 
         return false;
     }
 
-    public StatementSupport<?, ?,?> getStatementDefinition(QName stmtName) {
-        StatementSupport<?,?, ?> potential = definitions.get(stmtName);
+    public StatementSupport<?, ?,?> getStatementDefinition(final QName stmtName) {
+        final StatementSupport<?,?, ?> potential = definitions.get(stmtName);
         if (potential != null) {
             return potential;
         }
         if (potential != null) {
             return potential;
         }
@@ -85,31 +89,38 @@ public final class StatementSupportBundle implements Immutable,NamespaceBehaviou
 
     public static class Builder implements org.opendaylight.yangtools.concepts.Builder<StatementSupportBundle> {
 
 
     public static class Builder implements org.opendaylight.yangtools.concepts.Builder<StatementSupportBundle> {
 
-        private final StatementSupportBundle parent;
-        private final Map<QName, StatementSupport<?,?,?>> statements = new HashMap<>();
+        private StatementSupportBundle parent;
+        private final Map<QName, StatementSupport<?, ?, ?>> statements = new HashMap<>();
         private final Map<Class<?>, NamespaceBehaviour<?, ?, ?>> namespaces = new HashMap<>();
 
         private final Map<Class<?>, NamespaceBehaviour<?, ?, ?>> namespaces = new HashMap<>();
 
-        Builder(StatementSupportBundle parent) {
+        Builder(final StatementSupportBundle parent) {
             this.parent = parent;
         }
 
             this.parent = parent;
         }
 
-        public Builder addSupport(StatementSupport<?, ?,?> definition) {
-            QName identifier = definition.getStatementName();
-            Preconditions.checkState(!statements.containsKey(identifier), "Statement %s already defined.",identifier);
-            Preconditions.checkState(parent.getStatementDefinition(identifier) == null, "Statement %s already defined.",identifier);
+        public Builder addSupport(final StatementSupport<?, ?, ?> definition) {
+            final QName identifier = definition.getStatementName();
+            Preconditions.checkState(!statements.containsKey(identifier), "Statement %s already defined.", identifier);
+            Preconditions.checkState(parent.getStatementDefinition(identifier) == null,
+                    "Statement %s already defined.", identifier);
             statements.put(identifier, definition);
             return this;
         }
 
             statements.put(identifier, definition);
             return this;
         }
 
-       public <K, V, N extends IdentifierNamespace<K, V>> Builder addSupport(NamespaceBehaviour<K, V, N> namespaceSupport) {
-            Class<N> identifier = namespaceSupport.getIdentifier();
+        public <K, V, N extends IdentifierNamespace<K, V>> Builder addSupport(
+                final NamespaceBehaviour<K, V, N> namespaceSupport) {
+            final Class<N> identifier = namespaceSupport.getIdentifier();
             Preconditions.checkState(!namespaces.containsKey(identifier));
             Preconditions.checkState(!parent.hasNamespaceBehaviour(identifier));
             namespaces.put(identifier, namespaceSupport);
             return this;
         }
 
             Preconditions.checkState(!namespaces.containsKey(identifier));
             Preconditions.checkState(!parent.hasNamespaceBehaviour(identifier));
             namespaces.put(identifier, namespaceSupport);
             return this;
         }
 
-       @Override
+        public Builder setParent(final StatementSupportBundle parent) {
+            this.parent = parent;
+            return this;
+        }
+
+        @Override
         public StatementSupportBundle build() {
             return new StatementSupportBundle(parent, ImmutableMap.copyOf(statements), ImmutableMap.copyOf(namespaces));
         }
         public StatementSupportBundle build() {
             return new StatementSupportBundle(parent, ImmutableMap.copyOf(statements), ImmutableMap.copyOf(namespaces));
         }
index b58d750f4a6cb43e01d256eb6800880fca615185..8deb87e30b1608d096c1805ab6cce076b3fc39cd 100644 (file)
@@ -18,7 +18,7 @@ import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
 public interface ValidationBundlesNamespace extends
         IdentifierNamespace<ValidationBundlesNamespace.ValidationBundleType, Collection<?>> {
 
 public interface ValidationBundlesNamespace extends
         IdentifierNamespace<ValidationBundlesNamespace.ValidationBundleType, Collection<?>> {
 
-    enum ValidationBundleType {
+    public enum ValidationBundleType {
         /**
          * whether a node is suitable refine substatement
          */
         /**
          * whether a node is suitable refine substatement
          */
diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/CustomStatementParserBuilder.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/CustomStatementParserBuilder.java
new file mode 100644 (file)
index 0000000..7c648e4
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2016 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.yangtools.yang.parser.stmt.reactor;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
+import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupport;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupportBundle;
+import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace.ValidationBundleType;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor.Builder;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangInferencePipeline;
+
+public class CustomStatementParserBuilder {
+    private final Map<ModelProcessingPhase, StatementSupportBundle.Builder> reactorSupportBundles = ImmutableMap
+            .<ModelProcessingPhase, StatementSupportBundle.Builder> builder()
+            .put(ModelProcessingPhase.INIT, StatementSupportBundle.builder())
+            .put(ModelProcessingPhase.SOURCE_PRE_LINKAGE, StatementSupportBundle.builder())
+            .put(ModelProcessingPhase.SOURCE_LINKAGE, StatementSupportBundle.builder())
+            .put(ModelProcessingPhase.STATEMENT_DEFINITION, StatementSupportBundle.builder())
+            .put(ModelProcessingPhase.FULL_DECLARATION, StatementSupportBundle.builder())
+            .put(ModelProcessingPhase.EFFECTIVE_MODEL, StatementSupportBundle.builder()).build();
+    private final Map<ValidationBundleType, Collection<StatementDefinition>> reactorValidationBundles = new HashMap<>();
+
+    public CustomStatementParserBuilder addStatementSupport(final ModelProcessingPhase phase,
+            final StatementSupport<?, ?, ?> stmtSupport) {
+        reactorSupportBundles.get(phase).addSupport(stmtSupport);
+        return this;
+    }
+
+    public CustomStatementParserBuilder addNamespaceSupport(final ModelProcessingPhase phase,
+            final NamespaceBehaviour<?, ?, ?> namespaceSupport) {
+        reactorSupportBundles.get(phase).addSupport(namespaceSupport);
+        return this;
+    }
+
+    public CustomStatementParserBuilder addDefaultRFC6020Bundles() {
+        addRFC6020SupportBundles();
+        addRFC6020ValidationBundles();
+        return this;
+    }
+
+    private void addRFC6020ValidationBundles() {
+        reactorValidationBundles.putAll(YangInferencePipeline.RFC6020_VALIDATION_BUNDLE);
+    }
+
+    private void addRFC6020SupportBundles() {
+        for (final Entry<ModelProcessingPhase, StatementSupportBundle> entry : YangInferencePipeline.RFC6020_BUNDLES
+                .entrySet()) {
+            addAllSupports(entry.getKey(), entry.getValue());
+        }
+    }
+
+    public CustomStatementParserBuilder addValidationBundle(final ValidationBundleType validationBundleType,
+            final Collection<StatementDefinition> validationBundle) {
+        reactorValidationBundles.put(validationBundleType, validationBundle);
+        return this;
+    }
+
+    public CustomStatementParserBuilder addAllSupports(final ModelProcessingPhase phase,
+            final StatementSupportBundle stmtSupportBundle) {
+        addAllStatementSupports(phase, stmtSupportBundle.getDefinitions().values());
+        addAllNamespaceSupports(phase, stmtSupportBundle.getNamespaceDefinitions().values());
+        return this;
+    }
+
+    public CustomStatementParserBuilder addAllNamespaceSupports(final ModelProcessingPhase phase,
+            final Collection<NamespaceBehaviour<?, ?, ?>> namespaceSupports) {
+        final StatementSupportBundle.Builder stmtBundleBuilder = reactorSupportBundles.get(phase);
+        for (final NamespaceBehaviour<?, ?, ?> namespaceSupport : namespaceSupports) {
+            stmtBundleBuilder.addSupport(namespaceSupport);
+        }
+        return this;
+    }
+
+    public CustomStatementParserBuilder addAllStatementSupports(final ModelProcessingPhase phase,
+            final Collection<StatementSupport<?, ?, ?>> statementSupports) {
+        final StatementSupportBundle.Builder stmtBundleBuilder = reactorSupportBundles.get(phase);
+        for (final StatementSupport<?, ?, ?> statementSupport : statementSupports) {
+            stmtBundleBuilder.addSupport(statementSupport);
+        }
+        return this;
+    }
+
+    public CrossSourceStatementReactor build() {
+        final StatementSupportBundle initBundle = reactorSupportBundles.get(ModelProcessingPhase.INIT).build();
+        final StatementSupportBundle preLinkageBundle = reactorSupportBundles
+                .get(ModelProcessingPhase.SOURCE_PRE_LINKAGE).setParent(initBundle).build();
+        final StatementSupportBundle linkageBundle = reactorSupportBundles.get(ModelProcessingPhase.SOURCE_LINKAGE)
+                .setParent(preLinkageBundle).build();
+        final StatementSupportBundle stmtDefBundle = reactorSupportBundles
+                .get(ModelProcessingPhase.STATEMENT_DEFINITION).setParent(linkageBundle).build();
+        final StatementSupportBundle fullDeclBundle = reactorSupportBundles.get(ModelProcessingPhase.FULL_DECLARATION)
+                .setParent(stmtDefBundle).build();
+        final StatementSupportBundle effectiveBundle = reactorSupportBundles.get(ModelProcessingPhase.EFFECTIVE_MODEL)
+                .setParent(fullDeclBundle).build();
+
+        final Builder reactorBuilder = CrossSourceStatementReactor.builder()
+                .setBundle(ModelProcessingPhase.INIT, initBundle)
+                .setBundle(ModelProcessingPhase.SOURCE_PRE_LINKAGE, preLinkageBundle)
+                .setBundle(ModelProcessingPhase.SOURCE_LINKAGE, linkageBundle)
+                .setBundle(ModelProcessingPhase.STATEMENT_DEFINITION, stmtDefBundle)
+                .setBundle(ModelProcessingPhase.FULL_DECLARATION, fullDeclBundle)
+                .setBundle(ModelProcessingPhase.EFFECTIVE_MODEL, effectiveBundle);
+
+        for (final Entry<ValidationBundleType, Collection<StatementDefinition>> entry : reactorValidationBundles
+                .entrySet()) {
+            reactorBuilder.setValidationBundle(entry.getKey(), entry.getValue());
+        }
+
+        return reactorBuilder.build();
+    }
+}
index cf5450afad4305787f97e640fe62dcd2e1397647..344af3b6e6934b05425b5373982282c547a1c3a2 100644 (file)
@@ -25,7 +25,7 @@ public class ChildSchemaNodes<D extends DeclaredStatement<QName>,E extends Effec
     extends NamespaceBehaviour<QName, StmtContext<?, D, E>, ChildSchemaNodes<D, E>>
     implements StatementNamespace<QName, D, E>{
 
     extends NamespaceBehaviour<QName, StmtContext<?, D, E>, ChildSchemaNodes<D, E>>
     implements StatementNamespace<QName, D, E>{
 
-    protected ChildSchemaNodes() {
+    public ChildSchemaNodes() {
         super((Class) ChildSchemaNodes.class);
     }
 
         super((Class) ChildSchemaNodes.class);
     }
 
index ddcdd40afed4bdc3766dddc582b8d4701e3dd6bd..d33df59d97d0c981bd3296bb14f7e140873fdc88 100644 (file)
@@ -21,24 +21,24 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
 
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
 
-class SchemaNodeIdentifierBuildNamespace extends
+public class SchemaNodeIdentifierBuildNamespace extends
         DerivedNamespaceBehaviour<SchemaNodeIdentifier, StmtContext.Mutable<?, ?, EffectiveStatement<?, ?>>, QName, SchemaNodeIdentifierBuildNamespace, ChildSchemaNodes<?, ?>>
         implements IdentifierNamespace<SchemaNodeIdentifier, StmtContext.Mutable<?, ?, EffectiveStatement<?, ?>>> {
 
     @SuppressWarnings({"unchecked", "rawtypes"})
         DerivedNamespaceBehaviour<SchemaNodeIdentifier, StmtContext.Mutable<?, ?, EffectiveStatement<?, ?>>, QName, SchemaNodeIdentifierBuildNamespace, ChildSchemaNodes<?, ?>>
         implements IdentifierNamespace<SchemaNodeIdentifier, StmtContext.Mutable<?, ?, EffectiveStatement<?, ?>>> {
 
     @SuppressWarnings({"unchecked", "rawtypes"})
-    protected SchemaNodeIdentifierBuildNamespace() {
+    public SchemaNodeIdentifierBuildNamespace() {
         super(SchemaNodeIdentifierBuildNamespace.class, (Class) ChildSchemaNodes.class);
     }
 
     @Override
     public StmtContext.Mutable<?, ?, EffectiveStatement<?, ?>> get(
         super(SchemaNodeIdentifierBuildNamespace.class, (Class) ChildSchemaNodes.class);
     }
 
     @Override
     public StmtContext.Mutable<?, ?, EffectiveStatement<?, ?>> get(
-            SchemaNodeIdentifier key) {
+            final SchemaNodeIdentifier key) {
         throw new UnsupportedOperationException("Direct access to namespace is not supported");
     }
 
     @SuppressWarnings("unchecked")
     @Override
         throw new UnsupportedOperationException("Direct access to namespace is not supported");
     }
 
     @SuppressWarnings("unchecked")
     @Override
-    public StmtContext.Mutable<?, ?, EffectiveStatement<?, ?>> getFrom(NamespaceStorageNode storage, SchemaNodeIdentifier key) {
+    public StmtContext.Mutable<?, ?, EffectiveStatement<?, ?>> getFrom(final NamespaceStorageNode storage, final SchemaNodeIdentifier key) {
 
         final NamespaceStorageNode lookupStartStorage;
         if (key.isAbsolute() || storage.getStorageNodeType() == StorageNodeType.ROOT_STATEMENT_LOCAL) {
 
         final NamespaceStorageNode lookupStartStorage;
         if (key.isAbsolute() || storage.getStorageNodeType() == StorageNodeType.ROOT_STATEMENT_LOCAL) {
@@ -46,7 +46,7 @@ class SchemaNodeIdentifierBuildNamespace extends
         } else {
             lookupStartStorage = storage;
         }
         } else {
             lookupStartStorage = storage;
         }
-        Iterator<QName> iterator = key.getPathFromRoot().iterator();
+        final Iterator<QName> iterator = key.getPathFromRoot().iterator();
         if (!iterator.hasNext()) {
             if (lookupStartStorage instanceof StmtContext<?, ?, ?>) {
                 return (StmtContext.Mutable<?, ?, EffectiveStatement<?, ?>>) lookupStartStorage;
         if (!iterator.hasNext()) {
             if (lookupStartStorage instanceof StmtContext<?, ?, ?>) {
                 return (StmtContext.Mutable<?, ?, EffectiveStatement<?, ?>>) lookupStartStorage;
@@ -62,7 +62,7 @@ class SchemaNodeIdentifierBuildNamespace extends
         }
         while (current != null && iterator.hasNext()) {
             nextPath = iterator.next();
         }
         while (current != null && iterator.hasNext()) {
             nextPath = iterator.next();
-            StmtContext.Mutable<?, ?, EffectiveStatement<?, ?>> nextNodeCtx = (StmtContext.Mutable<?, ?, EffectiveStatement<?, ?>>) current
+            final StmtContext.Mutable<?, ?, EffectiveStatement<?, ?>> nextNodeCtx = (StmtContext.Mutable<?, ?, EffectiveStatement<?, ?>>) current
                     .getFromNamespace(ChildSchemaNodes.class, nextPath);
             if (nextNodeCtx == null) {
                 return tryToFindUnknownStatement(nextPath.getLocalName(), current);
                     .getFromNamespace(ChildSchemaNodes.class, nextPath);
             if (nextNodeCtx == null) {
                 return tryToFindUnknownStatement(nextPath.getLocalName(), current);
@@ -76,9 +76,9 @@ class SchemaNodeIdentifierBuildNamespace extends
     @SuppressWarnings({"unchecked", "rawtypes"})
     private static Mutable<?, ?, EffectiveStatement<?, ?>> tryToFindUnknownStatement(final String localName,
             final Mutable<?, ?, EffectiveStatement<?, ?>> current) {
     @SuppressWarnings({"unchecked", "rawtypes"})
     private static Mutable<?, ?, EffectiveStatement<?, ?>> tryToFindUnknownStatement(final String localName,
             final Mutable<?, ?, EffectiveStatement<?, ?>> current) {
-        Collection<StmtContext<?, ?, ?>> unknownSubstatements = (Collection)StmtContextUtils.findAllSubstatements(current,
+        final Collection<StmtContext<?, ?, ?>> unknownSubstatements = (Collection)StmtContextUtils.findAllSubstatements(current,
                 UnknownStatement.class);
                 UnknownStatement.class);
-        for (StmtContext<?, ?, ?> unknownSubstatement : unknownSubstatements) {
+        for (final StmtContext<?, ?, ?> unknownSubstatement : unknownSubstatements) {
             if (localName.equals(unknownSubstatement.rawStatementArgument())) {
                 return (Mutable<?, ?, EffectiveStatement<?, ?>>) unknownSubstatement;
             }
             if (localName.equals(unknownSubstatement.rawStatementArgument())) {
                 return (Mutable<?, ?, EffectiveStatement<?, ?>>) unknownSubstatement;
             }
@@ -87,7 +87,7 @@ class SchemaNodeIdentifierBuildNamespace extends
     }
 
     @Override
     }
 
     @Override
-    public QName getSignificantKey(SchemaNodeIdentifier key) {
+    public QName getSignificantKey(final SchemaNodeIdentifier key) {
         return key.getLastComponent();
     }
 
         return key.getLastComponent();
     }
 
index 35a958b55b87652d2dcccf764cb9a223e2f91e7d..92c844b18bd4b525f877a49df425770f9955624f 100644 (file)
@@ -10,9 +10,9 @@ package org.opendaylight.yangtools.yang.parser.stmt.rfc6020;
 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
 
 /**
 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
 
 /**
- * Implementation-internal cache for looking up URI -> import prefix. URIs are taken in as Strings to save ourselves
+ * Implementation-internal cache for looking up URI to import prefix. URIs are taken in as Strings to save ourselves
  * some quality parsing time.
  */
  * some quality parsing time.
  */
-interface URIStringToImpPrefix extends IdentifierNamespace<String, String> {
+public interface URIStringToImpPrefix extends IdentifierNamespace<String, String> {
 
 }
 
 }
index cfb9cd0ea781105649d9a098afb587b900e61fa7..e87aa4fa0c4e6992238746e088c546c64ecdc849 100644 (file)
@@ -12,7 +12,9 @@ import static org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour
 import static org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.treeScoped;
 
 import com.google.common.collect.ImmutableMap;
 import static org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.treeScoped;
 
 import com.google.common.collect.ImmutableMap;
+import java.util.Collection;
 import java.util.Map;
 import java.util.Map;
+import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
 import org.opendaylight.yangtools.yang.parser.spi.ExtensionNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.GroupingNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.IdentityNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.ExtensionNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.GroupingNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.IdentityNamespace;
@@ -107,7 +109,7 @@ public final class YangInferencePipeline {
             .addSupport(sourceLocal(ImpPrefixToSemVerModuleIdentifier.class))
             .build();
 
             .addSupport(sourceLocal(ImpPrefixToSemVerModuleIdentifier.class))
             .build();
 
-    private static final StatementSupportBundle STMT_DEF_BUNDLE = StatementSupportBundle
+    public static final StatementSupportBundle STMT_DEF_BUNDLE = StatementSupportBundle
             .derivedFrom(LINKAGE_BUNDLE)
             .addSupport(new YinElementStatementImpl.Definition())
             .addSupport(new ArgumentStatementImpl.Definition())
             .derivedFrom(LINKAGE_BUNDLE)
             .addSupport(new YinElementStatementImpl.Definition())
             .addSupport(new ArgumentStatementImpl.Definition())
@@ -143,7 +145,7 @@ public final class YangInferencePipeline {
             .addSupport(global(DerivedIdentitiesNamespace.class))
             .build();
 
             .addSupport(global(DerivedIdentitiesNamespace.class))
             .build();
 
-    private static final StatementSupportBundle FULL_DECL_BUNDLE = StatementSupportBundle
+    public static final StatementSupportBundle FULL_DECL_BUNDLE = StatementSupportBundle
             .derivedFrom(STMT_DEF_BUNDLE)
             .addSupport(new LeafStatementImpl.Definition())
             .addSupport(new ConfigStatementImpl.Definition())
             .derivedFrom(STMT_DEF_BUNDLE)
             .addSupport(new LeafStatementImpl.Definition())
             .addSupport(new ConfigStatementImpl.Definition())
@@ -179,6 +181,7 @@ public final class YangInferencePipeline {
 
     public static final Map<ModelProcessingPhase, StatementSupportBundle> RFC6020_BUNDLES = ImmutableMap
             .<ModelProcessingPhase, StatementSupportBundle> builder()
 
     public static final Map<ModelProcessingPhase, StatementSupportBundle> RFC6020_BUNDLES = ImmutableMap
             .<ModelProcessingPhase, StatementSupportBundle> builder()
+            .put(ModelProcessingPhase.INIT, INIT_BUNDLE)
             .put(ModelProcessingPhase.SOURCE_PRE_LINKAGE, PRE_LINKAGE_BUNDLE)
             .put(ModelProcessingPhase.SOURCE_LINKAGE, LINKAGE_BUNDLE)
             .put(ModelProcessingPhase.STATEMENT_DEFINITION, STMT_DEF_BUNDLE)
             .put(ModelProcessingPhase.SOURCE_PRE_LINKAGE, PRE_LINKAGE_BUNDLE)
             .put(ModelProcessingPhase.SOURCE_LINKAGE, LINKAGE_BUNDLE)
             .put(ModelProcessingPhase.STATEMENT_DEFINITION, STMT_DEF_BUNDLE)
@@ -186,6 +189,14 @@ public final class YangInferencePipeline {
             .put(ModelProcessingPhase.EFFECTIVE_MODEL, FULL_DECL_BUNDLE)
             .build();
 
             .put(ModelProcessingPhase.EFFECTIVE_MODEL, FULL_DECL_BUNDLE)
             .build();
 
+    public static final Map<ValidationBundleType, Collection<StatementDefinition>> RFC6020_VALIDATION_BUNDLE = ImmutableMap
+            .<ValidationBundleType, Collection<StatementDefinition>> builder()
+            .put(ValidationBundleType.SUPPORTED_REFINE_SUBSTATEMENTS, YangValidationBundles.SUPPORTED_REFINE_SUBSTATEMENTS)
+            .put(ValidationBundleType.SUPPORTED_AUGMENT_TARGETS, YangValidationBundles.SUPPORTED_AUGMENT_TARGETS)
+            .put(ValidationBundleType.SUPPORTED_CASE_SHORTHANDS, YangValidationBundles.SUPPORTED_CASE_SHORTHANDS)
+            .put(ValidationBundleType.SUPPORTED_DATA_NODES, YangValidationBundles.SUPPORTED_DATA_NODES)
+            .build();
+
     public static final CrossSourceStatementReactor RFC6020_REACTOR = CrossSourceStatementReactor
             .builder()
             .setBundle(ModelProcessingPhase.INIT, INIT_BUNDLE)
     public static final CrossSourceStatementReactor RFC6020_REACTOR = CrossSourceStatementReactor
             .builder()
             .setBundle(ModelProcessingPhase.INIT, INIT_BUNDLE)
diff --git a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/CustomInferencePipeline.java b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/CustomInferencePipeline.java
new file mode 100644 (file)
index 0000000..6f41833
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2016 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.yangtools.yang.thirdparty.plugin;
+
+import static org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.sourceLocal;
+
+import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.CustomStatementParserBuilder;
+
+public final class CustomInferencePipeline {
+    public static final CrossSourceStatementReactor CUSTOM_REACTOR = new CustomStatementParserBuilder()
+            .addDefaultRFC6020Bundles()
+            .addStatementSupport(ModelProcessingPhase.FULL_DECLARATION,
+                    new ThirdPartyExtensionStatementImpl.ThirdPartyExtensionSupport())
+            .addNamespaceSupport(ModelProcessingPhase.FULL_DECLARATION, sourceLocal(ThirdPartyNamespace.class))
+            .build();
+
+    private CustomInferencePipeline() {
+        throw new UnsupportedOperationException("Utility class");
+    }
+}
diff --git a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyExtensionEffectiveStatementImpl.java b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyExtensionEffectiveStatementImpl.java
new file mode 100644 (file)
index 0000000..0a7dcfa
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2016 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.yangtools.yang.thirdparty.plugin;
+
+import com.google.common.annotations.Beta;
+import java.util.Objects;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.api.stmt.UnknownStatement;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.UnknownEffectiveStatementBase;
+
+@Beta
+public final class ThirdPartyExtensionEffectiveStatementImpl extends UnknownEffectiveStatementBase<String> {
+
+    private final SchemaPath path;
+    private final String valueFromNamespace;
+
+    public ThirdPartyExtensionEffectiveStatementImpl(final StmtContext<String, UnknownStatement<String>, ?> ctx) {
+        super(ctx);
+        path = ctx.getParentContext().getSchemaPath().get().createChild(getNodeType());
+        valueFromNamespace = ctx.getFromNamespace(ThirdPartyNamespace.class, ctx);
+    }
+
+    public String getValueFromNamespace() {
+        return valueFromNamespace;
+    }
+
+    @Override
+    public QName getQName() {
+        return getNodeType();
+    }
+
+    @Override
+    public SchemaPath getPath() {
+        return path;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(path, getNodeType(), getNodeParameter());
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ThirdPartyExtensionEffectiveStatementImpl other = (ThirdPartyExtensionEffectiveStatementImpl) obj;
+        if (!Objects.equals(path, other.path)) {
+            return false;
+        }
+        if (!Objects.equals(getNodeType(), other.getNodeType())) {
+            return false;
+        }
+        if (!Objects.equals(getNodeParameter(), other.getNodeParameter())) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyExtensionPluginTest.java b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyExtensionPluginTest.java
new file mode 100644 (file)
index 0000000..4aca084
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2016 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.yangtools.yang.thirdparty.plugin;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.net.URISyntaxException;
+import java.util.List;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceImpl;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.EffectiveSchemaContext;
+
+public class ThirdPartyExtensionPluginTest {
+    private static final String NS = "urn:opendaylight:yang:extension:third-party";
+    private static final String REV = "2016-06-09";
+
+    @Test
+    public void test() throws FileNotFoundException, URISyntaxException, ReactorException {
+        final CrossSourceStatementReactor.BuildAction reactor = CustomInferencePipeline.CUSTOM_REACTOR.newBuild();
+        final FileInputStream yangFile = new FileInputStream(new File(getClass().getResource("/plugin-test/foo.yang")
+                .toURI()));
+        reactor.addSource(new YangStatementSourceImpl(yangFile));
+
+        final EffectiveSchemaContext schema = reactor.buildEffective();
+        final DataSchemaNode dataChildByName = schema.getDataChildByName(QName.create(NS, REV, "root"));
+        assertTrue(dataChildByName instanceof ContainerSchemaNode);
+        final ContainerSchemaNode root = (ContainerSchemaNode) dataChildByName;
+
+        final List<UnknownSchemaNode> unknownSchemaNodes = root.getUnknownSchemaNodes();
+        assertEquals(1, unknownSchemaNodes.size());
+
+        final UnknownSchemaNode unknownSchemaNode = unknownSchemaNodes.get(0);
+        assertTrue(unknownSchemaNode instanceof ThirdPartyExtensionEffectiveStatementImpl);
+
+        final ThirdPartyExtensionEffectiveStatementImpl thirdPartyExtensionStmt = (ThirdPartyExtensionEffectiveStatementImpl) unknownSchemaNode;
+        assertEquals("Third-party namespace test.", thirdPartyExtensionStmt.getValueFromNamespace());
+        assertEquals("plugin test", thirdPartyExtensionStmt.argument());
+    }
+}
diff --git a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyExtensionStatementImpl.java b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyExtensionStatementImpl.java
new file mode 100644 (file)
index 0000000..2636da8
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2016 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.yangtools.yang.thirdparty.plugin;
+
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.UnknownStatement;
+import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractDeclaredStatement;
+import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractStatementSupport;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
+
+public class ThirdPartyExtensionStatementImpl extends AbstractDeclaredStatement<String> implements
+        UnknownStatement<String> {
+
+    ThirdPartyExtensionStatementImpl(final StmtContext<String, UnknownStatement<String>, ?> context) {
+        super(context);
+    }
+
+    public static class ThirdPartyExtensionSupport
+            extends
+            AbstractStatementSupport<String, UnknownStatement<String>, EffectiveStatement<String, UnknownStatement<String>>> {
+
+        public ThirdPartyExtensionSupport() {
+            super(ThirdPartyExtensionsMapping.THIRD_PARTY_EXTENSION);
+        }
+
+        @Override
+        public String parseArgumentValue(final StmtContext<?, ?, ?> ctx, final String value) {
+            return value;
+        }
+
+        @Override
+        public void onFullDefinitionDeclared(
+                final Mutable<String, UnknownStatement<String>, EffectiveStatement<String, UnknownStatement<String>>> stmt) {
+            stmt.addToNs(ThirdPartyNamespace.class, stmt, "Third-party namespace test.");
+        }
+
+        @Override
+        public UnknownStatement<String> createDeclared(final StmtContext<String, UnknownStatement<String>, ?> ctx) {
+            return new ThirdPartyExtensionStatementImpl(ctx);
+        }
+
+        @Override
+        public EffectiveStatement<String, UnknownStatement<String>> createEffective(
+                final StmtContext<String, UnknownStatement<String>, EffectiveStatement<String, UnknownStatement<String>>> ctx) {
+            return new ThirdPartyExtensionEffectiveStatementImpl(ctx);
+        }
+    }
+
+    @Override
+    public String getArgument() {
+        return argument();
+    }
+}
diff --git a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyExtensionsMapping.java b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyExtensionsMapping.java
new file mode 100644 (file)
index 0000000..3e5bbd1
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2016 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.yangtools.yang.thirdparty.plugin;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
+
+@Beta
+public enum ThirdPartyExtensionsMapping implements StatementDefinition {
+    THIRD_PARTY_EXTENSION("urn:opendaylight:yang:extension:third-party", "2016-06-09",
+            ThirdPartyExtensionStatementImpl.class, ThirdPartyExtensionEffectiveStatementImpl.class,
+            "third-party-extension", "argument-name", false);
+
+    private final Class<? extends DeclaredStatement<?>> type;
+    private final Class<? extends EffectiveStatement<?, ?>> effectiveType;
+    private final QName name;
+    private final QName argument;
+    private final boolean yinElement;
+
+    ThirdPartyExtensionsMapping(final String namespace, final String revision,
+            final Class<? extends DeclaredStatement<?>> declared,
+            final Class<? extends EffectiveStatement<?, ?>> effective, final String nameStr, final String argumentStr,
+            final boolean yinElement) {
+        type = Preconditions.checkNotNull(declared);
+        effectiveType = Preconditions.checkNotNull(effective);
+        name = createQName(namespace, revision, nameStr);
+        argument = createQName(namespace, revision, argumentStr);
+        this.yinElement = yinElement;
+    }
+
+    private ThirdPartyExtensionsMapping(final String namespace, final Class<? extends DeclaredStatement<?>> declared,
+            final Class<? extends EffectiveStatement<?, ?>> effective, final String nameStr, final String argumentStr,
+            final boolean yinElement) {
+        type = Preconditions.checkNotNull(declared);
+        effectiveType = Preconditions.checkNotNull(effective);
+        name = createQName(namespace, nameStr);
+        argument = createQName(namespace, argumentStr);
+        this.yinElement = yinElement;
+    }
+
+    @Nonnull
+    private static QName createQName(final String namespace, final String localName) {
+        return QName.create(namespace, localName).intern();
+    }
+
+    @Nonnull
+    private static QName createQName(final String namespace, final String revision, final String localName) {
+        return QName.create(namespace, revision, localName).intern();
+    }
+
+    @Override
+    public QName getStatementName() {
+        return name;
+    }
+
+    @Override
+    @Nullable
+    public QName getArgumentName() {
+        return argument;
+    }
+
+    @Override
+    @Nonnull
+    public Class<? extends DeclaredStatement<?>> getDeclaredRepresentationClass() {
+        return type;
+    }
+
+    @Override
+    public Class<? extends EffectiveStatement<?, ?>> getEffectiveRepresentationClass() {
+        return effectiveType;
+    }
+
+    @Override
+    public boolean isArgumentYinElement() {
+        return yinElement;
+    }
+}
diff --git a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyNamespace.java b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/thirdparty/plugin/ThirdPartyNamespace.java
new file mode 100644 (file)
index 0000000..4942ee4
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2015 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.yangtools.yang.thirdparty.plugin;
+
+import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
+
+/**
+ *
+ * ThirdPartyNamespace
+ *
+ */
+public interface ThirdPartyNamespace extends IdentifierNamespace<StmtContext<?, ?, ?>, String> {
+
+}
diff --git a/yang/yang-parser-impl/src/test/resources/plugin-test/foo.yang b/yang/yang-parser-impl/src/test/resources/plugin-test/foo.yang
new file mode 100644 (file)
index 0000000..f9fd922
--- /dev/null
@@ -0,0 +1,15 @@
+module foo {
+    namespace "urn:opendaylight:yang:extension:third-party";
+    prefix foo;
+    yang-version 1;
+
+    revision 2016-06-09;
+
+    container root {
+        foo:third-party-extension "plugin test";
+    }
+
+    extension third-party-extension {
+        argument argument-name;
+    }
+}