Do not pretty-print body class
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / leafref / LeafRefPathParserImpl.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.yangtools.yang.data.impl.leafref;
9
10 import static com.google.common.base.Preconditions.checkState;
11
12 import com.google.common.collect.ImmutableList;
13 import java.util.ArrayDeque;
14 import java.util.Deque;
15 import java.util.Set;
16 import org.opendaylight.yangtools.yang.common.AbstractQName;
17 import org.opendaylight.yangtools.yang.common.QName;
18 import org.opendaylight.yangtools.yang.common.QNameModule;
19 import org.opendaylight.yangtools.yang.common.UnqualifiedQName;
20 import org.opendaylight.yangtools.yang.model.api.PathExpression;
21 import org.opendaylight.yangtools.yang.model.api.PathExpression.DerefSteps;
22 import org.opendaylight.yangtools.yang.model.api.PathExpression.LocationPathSteps;
23 import org.opendaylight.yangtools.yang.model.api.PathExpression.Steps;
24 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
25 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
26 import org.opendaylight.yangtools.yang.xpath.api.YangBinaryExpr;
27 import org.opendaylight.yangtools.yang.xpath.api.YangBinaryOperator;
28 import org.opendaylight.yangtools.yang.xpath.api.YangExpr;
29 import org.opendaylight.yangtools.yang.xpath.api.YangFunction;
30 import org.opendaylight.yangtools.yang.xpath.api.YangFunctionCallExpr;
31 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath;
32 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.QNameStep;
33 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Relative;
34 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Step;
35 import org.opendaylight.yangtools.yang.xpath.api.YangPathExpr;
36 import org.opendaylight.yangtools.yang.xpath.api.YangQNameExpr;
37
38 final class LeafRefPathParserImpl {
39     private final QNameModule leafrefModule;
40     private final QNameModule nodeModule;
41
42     LeafRefPathParserImpl(final LeafrefTypeDefinition leafrefType, final TypedDataSchemaNode currentNode) {
43         // FIXME: these two namespaces look not quite right:
44         //        - leafrefModule is used for absolute paths, irrespective of where they occur
45         //        - nodeModule is used for relative paths, irrespective of where they occur
46         //
47         // There is little in RFC7950 which would hint at such a distinction and if even if it were true, it would be
48         // the job of YANG parser to ensure that absolute paths are bound during parsing.
49         //
50         // The only distinction is relative to where the leafref is defined, namely:
51         //
52         // 1) as per section 9.9.2:
53         //     o  If the "path" statement is defined within a typedef, the context
54         //        node is the leaf or leaf-list node in the data tree that
55         //        references the typedef.
56         //
57         //     o  Otherwise, the context node is the node in the data tree for which
58         //        the "path" statement is defined.
59         //
60         // 2) as per section 6.4.1:
61         //     o  Names without a namespace prefix belong to the same namespace as
62         //        the identifier of the current node.  Inside a grouping, that
63         //        namespace is affected by where the grouping is used (see
64         //        Section 7.13).  Inside a typedef, that namespace is affected by
65         //        where the typedef is referenced.  If a typedef is defined and
66         //        referenced within a grouping, the namespace is affected by where
67         //        the grouping is used (see Section 7.13).
68         this.leafrefModule = getBaseModule(leafrefType);
69         this.nodeModule = currentNode.getQName().getModule();
70     }
71
72     LeafRefPath parseLeafRefPath(final PathExpression path) {
73         final Steps steps = path.getSteps();
74         if (steps instanceof LocationPathSteps) {
75             return parseLocationPath(((LocationPathSteps) steps).getLocationPath());
76         } else if (steps instanceof DerefSteps) {
77             throw new UnsupportedOperationException("deref() leafrefs are not implemented yet");
78         } else {
79             throw new IllegalStateException("Unsupported steps " + steps);
80         }
81     }
82
83     private LeafRefPath parseLocationPath(final YangLocationPath locationPath) {
84         return LeafRefPath.create(
85             createPathSteps(locationPath.isAbsolute() ? leafrefModule : nodeModule, locationPath.getSteps()),
86             locationPath.isAbsolute());
87     }
88
89     private static Deque<QNameWithPredicate> createPathSteps(final QNameModule localModule,
90             final ImmutableList<Step> steps) {
91         final Deque<QNameWithPredicate> path = new ArrayDeque<>(steps.size());
92         for (Step step : steps) {
93             switch (step.getAxis()) {
94                 case CHILD:
95                     checkState(step instanceof QNameStep, "Unsupported step %s", step);
96                     path.add(adaptChildStep((QNameStep) step, localModule));
97                     break;
98                 case PARENT:
99                     path.add(QNameWithPredicate.UP_PARENT);
100                     break;
101                 default:
102                     throw new IllegalStateException("Unsupported axis in step " + step);
103             }
104         }
105         return path;
106     }
107
108     private static QNameWithPredicate adaptChildStep(final QNameStep step, final QNameModule localModule) {
109         final QName qname = resolve(step.getQName(), localModule);
110         final Set<YangExpr> predicates = step.getPredicates();
111         if (predicates.isEmpty()) {
112             return new SimpleQNameWithPredicate(qname);
113         }
114
115         final QNameWithPredicateBuilder builder = new QNameWithPredicateBuilder(qname.getModule(),
116             qname.getLocalName());
117
118         for (YangExpr pred : predicates) {
119             final QNamePredicateBuilder predBuilder = new QNamePredicateBuilder();
120
121             if (pred instanceof YangBinaryExpr) {
122                 final YangBinaryExpr eqPred = (YangBinaryExpr) pred;
123                 checkState(eqPred.getOperator() == YangBinaryOperator.EQUALS);
124
125                 final YangExpr left = eqPred.getLeftExpr();
126                 checkState(left instanceof YangQNameExpr, "Unsupported left expression %s", left);
127                 predBuilder.setIdentifier(resolve(((YangQNameExpr) left).getQName(), localModule));
128
129                 final YangExpr right = eqPred.getRightExpr();
130                 if (right instanceof YangPathExpr) {
131                     final YangPathExpr rightPath = (YangPathExpr) right;
132                     final YangExpr filter = rightPath.getFilterExpr();
133                     if (filter instanceof YangFunctionCallExpr) {
134                         checkState(YangFunction.CURRENT.getIdentifier().equals(
135                             ((YangFunctionCallExpr) filter).getName()));
136                     } else {
137                         throw new IllegalStateException("Unhandled filter " + filter);
138                     }
139
140                     final Relative location = rightPath.getLocationPath()
141                             .orElseThrow(() -> new IllegalStateException("Missing locationPath in " + rightPath));
142                     predBuilder.setPathKeyExpression(LeafRefPath.create(
143                         createPathSteps(localModule, location.getSteps()), false));
144                 } else {
145                     throw new UnsupportedOperationException("Not implemented for " + right);
146                 }
147             }
148
149             builder.addQNamePredicate(predBuilder.build());
150         }
151
152         return builder.build();
153     }
154
155     private static QName resolve(final AbstractQName qname, final QNameModule localModule) {
156         if (qname instanceof QName) {
157             return (QName) qname;
158         } else if (qname instanceof UnqualifiedQName) {
159             // Bind to namespace. Note we expect to perform frequent matching, hence we are interning the result
160             return ((UnqualifiedQName) qname).bindTo(localModule).intern();
161         } else {
162             throw new IllegalStateException("Unhandled unresolved QName " + qname);
163         }
164     }
165
166     /**
167      * Find the first definition of supplied leafref type and return the module which contains this definition.
168      */
169     private static QNameModule getBaseModule(final LeafrefTypeDefinition leafrefType) {
170         LeafrefTypeDefinition current = leafrefType;
171         while (true) {
172             final LeafrefTypeDefinition base = current.getBaseType();
173             if (base == null) {
174                 return current.getQName().getModule();
175             }
176             current = base;
177         }
178     }
179 }