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