Skip to content

Package: Topic$Segment

Topic$Segment

nameinstructionbranchcomplexitylinemethod
account()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
applicationId()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
clientId()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
control()
M: 2 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
lambda$render$0(String)
M: 2 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
plain(String)
M: 25 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
plain(String[])
M: 11 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
raw(String)
M: 8 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
render()
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
replace(String)
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
wildcard()
M: 2 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*******************************************************************************
2: * Copyright (c) 2017, 2022 Red Hat Inc and others
3: *
4: * This program and the accompanying materials are made
5: * available under the terms of the Eclipse Public License 2.0
6: * which is available at https://www.eclipse.org/legal/epl-2.0/
7: *
8: * SPDX-License-Identifier: EPL-2.0
9: *
10: * Contributors:
11: * Red Hat Inc - initial API and implementation
12: *******************************************************************************/
13: package org.eclipse.kapua.kura.simulator.topic;
14:
15: import java.util.ArrayList;
16: import java.util.Arrays;
17: import java.util.Collections;
18: import java.util.HashMap;
19: import java.util.LinkedList;
20: import java.util.List;
21: import java.util.Map;
22: import java.util.Objects;
23: import java.util.stream.Collectors;
24:
25: import com.google.common.base.Function;
26:
27: public final class Topic {
28:
29: private static final String APPLICATION_ID = "application-id";
30:
31: private static final Segment CONTROL = new Segment() {
32:
33: @Override
34: public String render(final Function<String, String> replaceMapper) {
35: return "$EDC";
36: }
37:
38: @Override
39: public String toString() {
40: return render(null);
41: }
42: };
43:
44: private static final Segment WILDCARD = new Segment() {
45:
46: @Override
47: public String render(final Function<String, String> replaceMapper) {
48: return "#";
49: }
50:
51: @Override
52: public String toString() {
53: return render(null);
54: }
55: };
56:
57: public interface Segment {
58:
59: public String render(Function<String, String> replaceMapper);
60:
61: public default String render() {
62: return render(key -> null);
63: }
64:
65: public static Segment control() {
66: return CONTROL;
67: }
68:
69: public static Segment wildcard() {
70: return WILDCARD;
71: }
72:
73: public static Segment plain(final String segment) {
74: Objects.requireNonNull(segment);
75:• if (segment.isEmpty() || segment.contains("/")) {
76: throw new IllegalArgumentException(String.format("Illegal argument: '%s'", segment));
77: }
78:
79: return raw(segment);
80: }
81:
82: public static List<Segment> plain(final String... segment) {
83: Objects.requireNonNull(segment);
84: return Arrays.stream(segment).map(Segment::plain).collect(Collectors.toList());
85: }
86:
87: public static Segment raw(final String raw) {
88: Objects.requireNonNull(raw);
89:
90: return new Segment() {
91:
92: @Override
93: public String render(final Function<String, String> replaceMapper) {
94: return raw;
95: }
96:
97: @Override
98: public String toString() {
99: return render(null);
100: }
101: };
102: }
103:
104: public static Segment replace(final String key) {
105: return new ReplaceSegment(key);
106: }
107:
108: public static Segment account() {
109: return replace("account-name");
110: }
111:
112: public static Segment clientId() {
113: return replace("client-id");
114: }
115:
116: public static Segment applicationId() {
117: return replace(APPLICATION_ID);
118: }
119: }
120:
121: private static class ReplaceSegment implements Segment {
122:
123: private final String key;
124:
125: public ReplaceSegment(final String key) {
126: this.key = key;
127: }
128:
129: @Override
130: public String render(final Function<String, String> replaceMapper) {
131: final String value = replaceMapper.apply(this.key);
132: if (value == null || value.isEmpty()) {
133: throw new IllegalStateException(
134: String.format("Unable to replace segment '%s', no value found", this.key));
135: }
136: return value;
137: }
138:
139: @Override
140: public String toString() {
141: return "<" + this.key + ">";
142: }
143:
144: }
145:
146: private final List<Segment> segments;
147: private final Map<String, String> context = new HashMap<>();
148:
149: private Topic(final List<Segment> segments) {
150: this.segments = segments;
151: }
152:
153: private Topic(final Segment... segments) {
154: this.segments = Arrays.asList(segments);
155: }
156:
157: public List<Segment> getSegments() {
158: return Collections.unmodifiableList(this.segments);
159: }
160:
161: public String renderSegment(final Segment segment, final Map<String, String> context) {
162: return segment.render(key -> {
163: if (context.containsKey(key)) {
164: return context.get(key);
165: }
166: return Topic.this.context.get(key);
167: });
168: }
169:
170: public String render(final Map<String, String> context) {
171: return renderInternal(this.segments, context);
172: }
173:
174: /**
175: * Renders all tokens from {@code fromIndex} (inclusive) to {@code toIndex}
176: * (exclusive)
177: *
178: * @param fromIndex
179: * first item to render
180: * @param toIndex
181: * first item <strong>not</strong> to render
182: * @param context
183: * the additional context to use
184: * @return the rendered string
185: */
186: public String render(final int fromIndex, final int toIndex, final Map<String, String> context) {
187: return renderInternal(this.segments.subList(fromIndex, toIndex), context);
188: }
189:
190: public String render() {
191: return renderInternal(this.segments, Collections.emptyMap());
192: }
193:
194: /**
195: * Renders all tokens from {@code fromIndex} (inclusive) to {@code toIndex}
196: * (exclusive)
197: *
198: * @param fromIndex
199: * first item to render
200: * @param toIndex
201: * first item <strong>not</strong> to render
202: * @return the rendered string
203: */
204: public String render(final int fromIndex, final int toIndex) {
205: return renderInternal(this.segments.subList(fromIndex, toIndex), Collections.emptyMap());
206: }
207:
208: private String renderInternal(final List<Segment> segments, final Map<String, String> context) {
209: return segments.stream().map(seg -> renderSegment(seg, context)).collect(Collectors.joining("/"));
210: }
211:
212: public static Topic from(final List<Segment> segments) {
213: Objects.requireNonNull(segments);
214:
215: return new Topic(segments);
216: }
217:
218: public static Topic fromString(final String topic) {
219: return from(Arrays.stream(topic.split("\\/")).map(Segment::plain).collect(Collectors.toList()));
220: }
221:
222: public static Topic reply(final String requesterClientId, final String requestId) {
223: return new Topic(Segment.control(), Segment.account(), Segment.plain(requesterClientId), Segment.replace(APPLICATION_ID), Segment.plain("REPLY"),
224: Segment.plain(requestId));
225: }
226:
227: public static Topic notify(final String requesterClientId, final String... resource) {
228: final List<Segment> s = new LinkedList<>();
229: s.add(Segment.control());
230: s.add(Segment.account());
231: s.add(Segment.plain(requesterClientId));
232: s.add(Segment.replace(APPLICATION_ID));
233: s.add(Segment.plain("NOTIFY"));
234: s.addAll(Segment.plain(resource));
235: return new Topic(s);
236: }
237:
238: /**
239: * Get the topic for an application
240: * <p>
241: * <strong>Note:</strong> This is a topic for a control application, for sending data use a data application topic created by {@link #data(String)}.
242: * </p>
243: *
244: * @param application
245: * the application ID
246: * @return a new topic
247: */
248: public static Topic application(final String application) {
249: return new Topic(Segment.control(), Segment.account(), Segment.clientId(), Segment.plain(application));
250: }
251:
252: public static Topic device(final String localTopic) {
253: return new Topic(Segment.control(), Segment.account(), Segment.clientId(), Segment.raw(localTopic));
254: }
255:
256: public static Topic data(final String dataTopic) {
257: if (dataTopic.isEmpty()) {
258: throw new IllegalArgumentException("Data topic must not be empty");
259: }
260: if (dataTopic.contains("#") || dataTopic.contains("+")) {
261: throw new IllegalArgumentException("Data topic must not contain wildcards");
262: }
263: if (dataTopic.startsWith("/")) {
264: throw new IllegalArgumentException("Data topic must not start with /");
265: }
266: return new Topic(Segment.account(), Segment.clientId(), Segment.applicationId(), Segment.raw(dataTopic));
267: }
268:
269: public Topic append(final Segment segment) {
270: final List<Segment> segs = new ArrayList<>(this.segments.size() + 1);
271: segs.addAll(this.segments);
272: segs.add(segment);
273: return new Topic(segs);
274: }
275:
276: /**
277: * Attach information to the local topic context
278: *
279: * @param key
280: * the key of the value
281: * @param value
282: * the value to attach
283: * @return the current instance
284: */
285: public Topic attach(final String key, final String value) {
286: this.context.put(key, value);
287: return this;
288: }
289:
290: public Topic attachAll(final Map<String, String> topicContext) {
291: this.context.putAll(topicContext);
292: return this;
293: }
294:
295: public Topic localize(final Topic otherTopic) {
296: return localize(otherTopic, Collections.emptyMap());
297: }
298:
299: public Topic localize(final Topic otherTopic, final Map<String, String> topicContext) {
300: final LinkedList<Segment> newTopic = new LinkedList<>(getSegments());
301:
302: for (final Segment seg : otherTopic.getSegments()) {
303: final String segValue1 = renderSegment(newTopic.removeFirst(), topicContext);
304: final String segValue2 = otherTopic.renderSegment(seg, topicContext);
305:
306: if (!segValue1.equals(segValue2)) {
307: return null;
308: }
309: }
310:
311: return Topic.from(newTopic);
312: }
313:
314: @Override
315: public String toString() {
316: return this.segments.stream().map(Object::toString).collect(Collectors.joining("/"));
317: }
318:
319: public Map<String, String> getContext() {
320: return Collections.unmodifiableMap(this.context);
321: }
322: }