Update RFC links
[netconf.git] / protocol / restconf-api / src / main / java / org / opendaylight / restconf / api / query / FieldsParam.java
1 /*
2  * Copyright (c) 2021 PANTHEON.tech, s.r.o. 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.restconf.api.query;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.base.MoreObjects;
14 import com.google.common.collect.ImmutableList;
15 import java.net.URI;
16 import java.text.ParseException;
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.opendaylight.restconf.api.ApiPath.ApiIdentifier;
19 import org.opendaylight.yangtools.concepts.Immutable;
20
21 /**
22  * This class represents a {@code fields} parameter as defined in
23  * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.8.3">RFC8040 section 4.8.3</a>.
24  */
25 @NonNullByDefault
26 public final class FieldsParam implements RestconfQueryParam<FieldsParam> {
27     /**
28      * A selector for a single node as identified by {@link #path()}. Individual child nodes are subject to further
29      * filtering based on {@link #subSelectors()}.
30      */
31     public static final class NodeSelector implements Immutable {
32         private final ImmutableList<ApiIdentifier> path;
33         private final ImmutableList<NodeSelector> subSelectors;
34
35         NodeSelector(final ImmutableList<ApiIdentifier> path, final ImmutableList<NodeSelector> subSelectors) {
36             this.path = requireNonNull(path);
37             this.subSelectors = requireNonNull(subSelectors);
38             checkArgument(!path.isEmpty(), "At least path segment is required");
39         }
40
41         /**
42          * Return the path to the selected node. Guaranteed to have at least one element.
43          *
44          * @return path to the selected node
45          */
46         public ImmutableList<ApiIdentifier> path() {
47             return path;
48         }
49
50         /**
51          * Selectors for single nodes which should be selected from the node found by interpreting {@link #path}. If
52          * there are no selectors, i.e. {@code subSelectors().isEmpty())}, all child nodes are meant to be selected.
53          *
54          * @return Selectors for nested nodes.
55          */
56         public ImmutableList<NodeSelector> subSelectors() {
57             return subSelectors;
58         }
59
60         @Override
61         public String toString() {
62             final var helper = MoreObjects.toStringHelper(this).add("path", path);
63             if (!subSelectors.isEmpty()) {
64                 helper.add("subSelectors", subSelectors);
65             }
66             return helper.toString();
67         }
68
69         void appendTo(final StringBuilder sb) {
70             final var it = path.iterator();
71             appendStep(sb, it.next());
72             while (it.hasNext()) {
73                 appendStep(sb.append('/'), it.next());
74             }
75
76             if (!subSelectors.isEmpty()) {
77                 appendSelectors(sb.append('('), subSelectors).append(')');
78             }
79         }
80
81         private static void appendStep(final StringBuilder sb, final ApiIdentifier step) {
82             final var mod = step.module();
83             if (mod != null) {
84                 sb.append(mod).append(':');
85             }
86             sb.append(step.identifier().getLocalName());
87         }
88     }
89
90     // API consistency: must not be confused with enum constants
91     @SuppressWarnings("checkstyle:ConstantName")
92     public static final String uriName = "fields";
93     private static final URI CAPABILITY = URI.create("urn:ietf:params:restconf:capability:fields:1.0");
94
95     private final ImmutableList<NodeSelector> nodeSelectors;
96
97     FieldsParam(final ImmutableList<NodeSelector> nodeSelectors) {
98         this.nodeSelectors = requireNonNull(nodeSelectors);
99         checkArgument(!nodeSelectors.isEmpty(), "At least one selector is required");
100     }
101
102     /**
103      * Parse a {@code fields} parameter.
104      *
105      * @param str Unescaped URL string
106      * @return The contents of parameter
107      * @throws ParseException if {@code str} does not represent a valid {@code fields} parameter.
108      */
109     public static FieldsParam parse(final String str) throws ParseException {
110         return new FieldsParameterParser().parse(str);
111     }
112
113     public static FieldsParam forUriValue(final String uriValue) {
114         try {
115             return parse(uriValue);
116         } catch (ParseException e) {
117             throw new IllegalArgumentException(e.getMessage() + " [at offset " + e.getErrorOffset() + "]", e);
118         }
119     }
120
121     @Override
122     public Class<FieldsParam> javaClass() {
123         return FieldsParam.class;
124     }
125
126     @Override
127     public String paramName() {
128         return uriName;
129     }
130
131     public static URI capabilityUri() {
132         return CAPABILITY;
133     }
134
135     /**
136      * Selectors for nodes which should be reported. Guaranteed to have at least one element.
137      *
138      * @return selectors for nodes to be reported
139      */
140     public ImmutableList<NodeSelector> nodeSelectors() {
141         return nodeSelectors;
142     }
143
144     @Override
145     public String paramValue() {
146         return appendSelectors(new StringBuilder(), nodeSelectors).toString();
147     }
148
149     @Override
150     public String toString() {
151         return MoreObjects.toStringHelper(this).add("nodeSelectors", nodeSelectors).toString();
152     }
153
154     private static StringBuilder appendSelectors(final StringBuilder sb, final ImmutableList<NodeSelector> selectors) {
155         final var it = selectors.iterator();
156         it.next().appendTo(sb);
157         while (it.hasNext()) {
158             it.next().appendTo(sb.append(';'));
159         }
160         return sb;
161     }
162 }