Convert restconf-api to a JPMS module
[netconf.git] / protocol / restconf-api / src / main / java / org / opendaylight / restconf / api / ApiPath.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;
9
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.base.MoreObjects;
14 import com.google.common.base.MoreObjects.ToStringHelper;
15 import com.google.common.collect.ImmutableList;
16 import java.text.ParseException;
17 import java.util.Objects;
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.opendaylight.yangtools.concepts.Immutable;
21 import org.opendaylight.yangtools.yang.common.UnresolvedQName;
22 import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
23
24 /**
25  * Intermediate representation of a parsed {@code api-path} string as defined in
26  * <a href="https://datatracker.ietf.org/doc/html/rfc8040#section-3.5.3.1">RFC section 3.5.3.1</a>. It models the
27  * path as a series of {@link Step}s.
28  */
29 @NonNullByDefault
30 public final class ApiPath implements Immutable {
31     /**
32      * A single step in an {@link ApiPath}.
33      */
34     public abstract static sealed class Step implements Immutable {
35         private final @Nullable String module;
36         private final Unqualified identifier;
37
38         Step(final @Nullable String module, final String identifier) {
39             this.identifier = verifyNotNull(UnresolvedQName.tryLocalName(identifier),
40                 "Unexpected invalid identifier %s", identifier);
41             this.module = module;
42         }
43
44         public Unqualified identifier() {
45             return identifier;
46         }
47
48         public @Nullable String module() {
49             return module;
50         }
51
52         @Override
53         public abstract int hashCode();
54
55         @Override
56         public abstract boolean equals(@Nullable Object obj);
57
58         final boolean equals(final Step other) {
59             return Objects.equals(module, other.module) && identifier.equals(other.identifier);
60         }
61
62         @Override
63         public final String toString() {
64             return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString();
65         }
66
67         ToStringHelper addToStringAttributes(final ToStringHelper helper) {
68             return helper.add("module", module).add("identifier", identifier);
69         }
70     }
71
72     /**
73      * An {@code api-identifier} step in a {@link ApiPath}.
74      */
75     public static final class ApiIdentifier extends Step {
76         public ApiIdentifier(final @Nullable String module, final String identifier) {
77             super(module, identifier);
78         }
79
80         @Override
81         public int hashCode() {
82             return Objects.hash(module(), identifier());
83         }
84
85         @Override
86         public boolean equals(final @Nullable Object obj) {
87             return this == obj || obj instanceof ApiIdentifier other && equals(other);
88         }
89     }
90
91     /**
92      * A {@code list-instance} step in a {@link ApiPath}.
93      */
94     public static final class ListInstance extends Step {
95         private final ImmutableList<String> keyValues;
96
97         ListInstance(final @Nullable String module, final String identifier, final ImmutableList<String> keyValues) {
98             super(module, identifier);
99             this.keyValues = requireNonNull(keyValues);
100         }
101
102         public ImmutableList<String> keyValues() {
103             return keyValues;
104         }
105
106         @Override
107         public int hashCode() {
108             return Objects.hash(module(), identifier(), keyValues);
109         }
110
111         @Override
112         public boolean equals(final @Nullable Object obj) {
113             return this == obj || obj instanceof ListInstance other && equals(other)
114                 && keyValues.equals(other.keyValues);
115         }
116
117         @Override
118         ToStringHelper addToStringAttributes(final ToStringHelper helper) {
119             return super.addToStringAttributes(helper).add("keyValues", keyValues);
120         }
121     }
122
123     private static final ApiPath EMPTY = new ApiPath(ImmutableList.of());
124
125     private final ImmutableList<Step> steps;
126
127     private ApiPath(final ImmutableList<Step> steps) {
128         this.steps = requireNonNull(steps);
129     }
130
131     /**
132      * Return an empty ApiPath.
133      *
134      * @return An empty ApiPath.
135      */
136     public static ApiPath empty() {
137         return EMPTY;
138     }
139
140     /**
141      * Parse an {@link ApiPath} from a raw Request URI fragment or another source. The string is expected to contain
142      * percent-encoded bytes. Any sequence of such bytes is interpreted as a {@code UTF-8}-encoded string. Invalid
143      * sequences are rejected.
144      *
145      * @param str Request URI part
146      * @return An {@link ApiPath}
147      * @throws NullPointerException if {@code str} is {@code null}
148      * @throws ParseException if the string cannot be parsed
149      */
150     public static ApiPath parse(final String str) throws ParseException {
151         return str.isEmpty() ? EMPTY : parseString(ApiPathParser.newStrict(), str);
152     }
153
154     /**
155      * Parse an {@link ApiPath} from a raw Request URI fragment. The string is expected to contain percent-encoded
156      * bytes. Any sequence of such bytes is interpreted as a {@code UTF-8}-encoded string. Invalid sequences are
157      * rejected, but consecutive slashes may be tolerated, depending on runtime configuration.
158      *
159      * @param str Request URI part
160      * @return An {@link ApiPath}
161      * @throws NullPointerException if {@code str} is {@code null}
162      * @throws ParseException if the string cannot be parsed
163      */
164     public static ApiPath parseUrl(final String str) throws ParseException {
165         return str.isEmpty() ? EMPTY : parseString(ApiPathParser.newUrl(), str);
166     }
167
168     public ImmutableList<Step> steps() {
169         return steps;
170     }
171
172     public int indexOf(final String module, final String identifier) {
173         final var m = requireNonNull(module);
174         final var id = requireNonNull(identifier);
175         for (int i = 0, size = steps.size(); i < size; ++i) {
176             final var step = steps.get(i);
177             if (m.equals(step.module) && id.equals(step.identifier.getLocalName())) {
178                 return i;
179             }
180         }
181         return -1;
182     }
183
184     public ApiPath subPath(final int fromIndex) {
185         return subPath(fromIndex, steps.size());
186     }
187
188     public ApiPath subPath(final int fromIndex, final int toIndex) {
189         final var subList = steps.subList(fromIndex, toIndex);
190         if (subList == steps) {
191             return this;
192         } else if (subList.isEmpty()) {
193             return EMPTY;
194         } else {
195             return new ApiPath(subList);
196         }
197     }
198
199     @Override
200     public int hashCode() {
201         return steps.hashCode();
202     }
203
204     @Override
205     public boolean equals(final @Nullable Object obj) {
206         return obj == this || obj instanceof ApiPath other && steps.equals(other.steps());
207     }
208
209     private static ApiPath parseString(final ApiPathParser parser, final String str) throws ParseException {
210         final var steps = parser.parseSteps(str);
211         return steps.isEmpty() ? EMPTY : new ApiPath(steps);
212     }
213 }