001/*******************************************************************************
002 * Copyright (c) 2017 Red Hat Inc and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Red Hat Inc - initial API and implementation
011 *******************************************************************************/
012package org.eclipse.kapua.gateway.client;
013
014import java.util.ArrayList;
015import java.util.Arrays;
016import java.util.Collections;
017import java.util.List;
018import java.util.stream.Stream;
019
020/**
021 * A topic
022 * <p>
023 * <b>Note:</b> This is not a technical MQTT topic, but an internal data topic.
024 * For this reason special topics like wildcards are not supported and will cause
025 * an {@link Exception}.
026 * </p>
027 */
028public final class Topic {
029
030    private final List<String> segments;
031
032    private Topic(final List<String> segments) {
033        this.segments = Collections.unmodifiableList(segments);
034    }
035
036    public List<String> getSegments() {
037        return segments;
038    }
039
040    public Stream<String> stream() {
041        return segments.stream();
042    }
043
044    @Override
045    public String toString() {
046        return String.join("/", segments);
047    }
048
049    @Override
050    public int hashCode() {
051        final int prime = 31;
052        int result = 1;
053        result = prime * result + (segments == null ? 0 : segments.hashCode());
054        return result;
055    }
056
057    @Override
058    public boolean equals(Object obj) {
059        if (this == obj) {
060            return true;
061        }
062        if (obj == null) {
063            return false;
064        }
065        if (getClass() != obj.getClass()) {
066            return false;
067        }
068        Topic other = (Topic) obj;
069        if (segments == null) {
070            if (other.segments != null) {
071                return false;
072            }
073        } else if (!segments.equals(other.segments)) {
074            return false;
075        }
076        return true;
077    }
078
079    public static Topic split(String path) {
080        if (path == null) {
081            return null;
082        }
083
084        path = path.replaceAll("(^/+|/$)+", "");
085
086        if (path.isEmpty()) {
087            return null;
088        }
089
090        return new Topic(Arrays.asList(path.split("\\/+")));
091    }
092
093    public static Topic of(final List<String> segments) {
094        if (segments == null || segments.isEmpty()) {
095            return null;
096        }
097
098        segments.forEach(Topic::ensureNotSpecial);
099
100        return new Topic(new ArrayList<>(segments));
101    }
102
103    public static Topic of(final String first, final String... strings) {
104        if (first == null) {
105            return null;
106        }
107
108        if (strings == null || strings.length <= 0) {
109            return new Topic(Collections.singletonList(ensureNotSpecial(first)));
110        }
111
112        final List<String> segments = new ArrayList<>(1 + strings.length);
113        segments.add(ensureNotSpecial(first));
114        for (final String segment : strings) {
115            segments.add(ensureNotSpecial(segment));
116        }
117        return new Topic(segments);
118    }
119
120    public static String ensureNotSpecial(final String segment) {
121        if (segment == null || segment.isEmpty()) {
122            throw new IllegalArgumentException("Segment must not be null or empty");
123        } else if ("#".equals(segment)) {
124            throw new IllegalArgumentException("Wildcard topics are not allowed");
125        } else if ("+".equals(segment)) {
126            throw new IllegalArgumentException("Wildcard topics are not allowed");
127        } else if (segment.contains("/")) {
128            throw new IllegalArgumentException("Segments must not contain slashes. Use Topic.split to parse a multi-segment topic string.");
129        }
130        return segment;
131    }
132
133}