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}