|
15 | 15 | */ |
16 | 16 | package org.eclipse.paho.mqttv5.client; |
17 | 17 |
|
18 | | -import java.io.UnsupportedEncodingException; |
19 | | - |
20 | 18 | import org.eclipse.paho.mqttv5.client.internal.ClientComms; |
21 | | -import org.eclipse.paho.mqttv5.client.util.Strings; |
22 | 19 | import org.eclipse.paho.mqttv5.common.MqttException; |
23 | 20 | import org.eclipse.paho.mqttv5.common.MqttMessage; |
24 | 21 | import org.eclipse.paho.mqttv5.common.MqttPersistenceException; |
|
30 | 27 | */ |
31 | 28 | public class MqttTopic { |
32 | 29 |
|
33 | | - /** |
34 | | - * The forward slash (/) is used to separate each level within a topic tree and |
35 | | - * provide a hierarchical structure to the topic space. The use of the topic |
36 | | - * level separator is significant when the two wildcard characters are |
37 | | - * encountered in topics specified by subscribers. |
38 | | - */ |
39 | | - public static final String TOPIC_LEVEL_SEPARATOR = "/"; |
40 | | - |
41 | | - /** |
42 | | - * Multi-level wildcard The number sign (#) is a wildcard character that matches |
43 | | - * any number of levels within a topic. |
44 | | - */ |
45 | | - public static final String MULTI_LEVEL_WILDCARD = "#"; |
46 | | - |
47 | | - /** |
48 | | - * Single-level wildcard The plus sign (+) is a wildcard character that matches |
49 | | - * only one topic level. |
50 | | - */ |
51 | | - public static final String SINGLE_LEVEL_WILDCARD = "+"; |
52 | | - |
53 | | - /** |
54 | | - * Multi-level wildcard pattern(/#) |
55 | | - */ |
56 | | - public static final String MULTI_LEVEL_WILDCARD_PATTERN = TOPIC_LEVEL_SEPARATOR + MULTI_LEVEL_WILDCARD; |
57 | | - |
58 | | - /** |
59 | | - * Topic wildcards (#+) |
60 | | - */ |
61 | | - public static final String TOPIC_WILDCARDS = MULTI_LEVEL_WILDCARD + SINGLE_LEVEL_WILDCARD; |
62 | | - |
63 | | - // topic name and topic filter length range defined in the spec |
64 | | - private static final int MIN_TOPIC_LEN = 1; |
65 | | - private static final int MAX_TOPIC_LEN = 65535; |
66 | | - private static final char NUL = '\u0000'; |
67 | 30 |
|
68 | 31 | private ClientComms comms; |
69 | 32 | private String name; |
@@ -159,159 +122,4 @@ public String toString() { |
159 | 122 | return getName(); |
160 | 123 | } |
161 | 124 |
|
162 | | - /** |
163 | | - * Validate the topic name or topic filter |
164 | | - * |
165 | | - * @param topicString |
166 | | - * topic name or filter |
167 | | - * @param wildcardAllowed |
168 | | - * true if validate topic filter, false otherwise |
169 | | - * @throws IllegalArgumentException |
170 | | - * if the topic is invalid |
171 | | - */ |
172 | | - public static void validate(String topicString, boolean wildcardAllowed) throws IllegalArgumentException { |
173 | | - int topicLen = 0; |
174 | | - try { |
175 | | - topicLen = topicString.getBytes("UTF-8").length; |
176 | | - } catch (UnsupportedEncodingException e) { |
177 | | - throw new IllegalStateException(e.getMessage()); |
178 | | - } |
179 | | - |
180 | | - // Spec: length check |
181 | | - // - All Topic Names and Topic Filters MUST be at least one character |
182 | | - // long |
183 | | - // - Topic Names and Topic Filters are UTF-8 encoded strings, they MUST |
184 | | - // NOT encode to more than 65535 bytes |
185 | | - if (topicLen < MIN_TOPIC_LEN || topicLen > MAX_TOPIC_LEN) { |
186 | | - throw new IllegalArgumentException(String.format("Invalid topic length, should be in range[%d, %d]!", |
187 | | - new Object[] { new Integer(MIN_TOPIC_LEN), new Integer(MAX_TOPIC_LEN) })); |
188 | | - } |
189 | | - |
190 | | - // ******************************************************************************* |
191 | | - // 1) This is a topic filter string that can contain wildcard characters |
192 | | - // ******************************************************************************* |
193 | | - if (wildcardAllowed) { |
194 | | - // Only # or + |
195 | | - if (Strings.equalsAny(topicString, new String[] { MULTI_LEVEL_WILDCARD, SINGLE_LEVEL_WILDCARD })) { |
196 | | - return; |
197 | | - } |
198 | | - |
199 | | - // 1) Check multi-level wildcard |
200 | | - // Rule: |
201 | | - // The multi-level wildcard can be specified only on its own or next |
202 | | - // to the topic level separator character. |
203 | | - |
204 | | - // - Can only contains one multi-level wildcard character |
205 | | - // - The multi-level wildcard must be the last character used within |
206 | | - // the topic tree |
207 | | - if (Strings.countMatches(topicString, MULTI_LEVEL_WILDCARD) > 1 |
208 | | - || (topicString.contains(MULTI_LEVEL_WILDCARD) |
209 | | - && !topicString.endsWith(MULTI_LEVEL_WILDCARD_PATTERN))) { |
210 | | - throw new IllegalArgumentException( |
211 | | - "Invalid usage of multi-level wildcard in topic string: " + topicString); |
212 | | - } |
213 | | - |
214 | | - // 2) Check single-level wildcard |
215 | | - // Rule: |
216 | | - // The single-level wildcard can be used at any level in the topic |
217 | | - // tree, and in conjunction with the |
218 | | - // multilevel wildcard. It must be used next to the topic level |
219 | | - // separator, except when it is specified on |
220 | | - // its own. |
221 | | - validateSingleLevelWildcard(topicString); |
222 | | - |
223 | | - return; |
224 | | - } |
225 | | - |
226 | | - // ******************************************************************************* |
227 | | - // 2) This is a topic name string that MUST NOT contains any wildcard characters |
228 | | - // ******************************************************************************* |
229 | | - if (Strings.containsAny(topicString, TOPIC_WILDCARDS)) { |
230 | | - throw new IllegalArgumentException("The topic name MUST NOT contain any wildcard characters (#+)"); |
231 | | - } |
232 | | - } |
233 | | - |
234 | | - private static void validateSingleLevelWildcard(String topicString) { |
235 | | - char singleLevelWildcardChar = SINGLE_LEVEL_WILDCARD.charAt(0); |
236 | | - char topicLevelSeparatorChar = TOPIC_LEVEL_SEPARATOR.charAt(0); |
237 | | - |
238 | | - char[] chars = topicString.toCharArray(); |
239 | | - int length = chars.length; |
240 | | - char prev = NUL, next = NUL; |
241 | | - for (int i = 0; i < length; i++) { |
242 | | - prev = (i - 1 >= 0) ? chars[i - 1] : NUL; |
243 | | - next = (i + 1 < length) ? chars[i + 1] : NUL; |
244 | | - |
245 | | - if (chars[i] == singleLevelWildcardChar) { |
246 | | - // prev and next can be only '/' or none |
247 | | - if (prev != topicLevelSeparatorChar && prev != NUL || next != topicLevelSeparatorChar && next != NUL) { |
248 | | - throw new IllegalArgumentException( |
249 | | - String.format("Invalid usage of single-level wildcard in topic string '%s'!", |
250 | | - new Object[] { topicString })); |
251 | | - |
252 | | - } |
253 | | - } |
254 | | - } |
255 | | - } |
256 | | - |
257 | | - /** |
258 | | - * Check the supplied topic name and filter match |
259 | | - * |
260 | | - * @param topicFilter |
261 | | - * topic filter: wildcards allowed |
262 | | - * @param topicName |
263 | | - * topic name: wildcards not allowed |
264 | | - * @return true if the topic matches the filter |
265 | | - * @throws IllegalArgumentException |
266 | | - * if the topic name or filter is invalid |
267 | | - */ |
268 | | - public static boolean isMatched(String topicFilter, String topicName) throws IllegalArgumentException { |
269 | | - int topicPos = 0; |
270 | | - int filterPos = 0; |
271 | | - int topicLen = topicName.length(); |
272 | | - int filterLen = topicFilter.length(); |
273 | | - |
274 | | - MqttTopic.validate(topicFilter, true); |
275 | | - MqttTopic.validate(topicName, false); |
276 | | - |
277 | | - if (topicFilter.equals(topicName)) { |
278 | | - return true; |
279 | | - } |
280 | | - |
281 | | - while (filterPos < filterLen && topicPos < topicLen) { |
282 | | - if (topicName.charAt(topicPos) == '/' && topicFilter.charAt(filterPos) != '/') |
283 | | - |
284 | | - break; |
285 | | - if (topicFilter.charAt(filterPos) != '+' && topicFilter.charAt(filterPos) != '#' |
286 | | - && topicFilter.charAt(filterPos) != topicName.charAt(topicPos)) |
287 | | - break; |
288 | | - if (topicFilter.charAt(filterPos) == '+') { // skip until we meet the next separator, or end of string |
289 | | - int nextpos = topicPos + 1; |
290 | | - while (nextpos < topicLen && topicName.charAt(nextpos) != '/') |
291 | | - nextpos = ++topicPos + 1; |
292 | | - } else if (topicFilter.charAt(filterPos) == '#') |
293 | | - topicPos = topicLen - 1; // skip until end of string |
294 | | - filterPos++; |
295 | | - topicPos++; |
296 | | - } |
297 | | - |
298 | | - if ((topicPos == topicLen) && (filterPos == filterLen)) { |
299 | | - return true; |
300 | | - } else { |
301 | | - /* |
302 | | - * https://github.com/eclipse/paho.mqtt.java/issues/418 |
303 | | - * Covers edge case to match sport/# to sport |
304 | | - */ |
305 | | - if ((topicFilter.length() - topicName.length()) == 2 && |
306 | | - topicFilter.substring(topicFilter.length() -2, topicFilter.length()).equals("/#")) { |
307 | | - String filterSub = topicFilter.substring(0, topicFilter.length() - 2); |
308 | | - if (filterSub.equals(topicName)) { |
309 | | - System.err.println("filterSub equals topicName: " + filterSub + " == " + topicName); |
310 | | - return true; |
311 | | - } |
312 | | - } |
313 | | - } |
314 | | - return false; |
315 | | - } |
316 | | - |
317 | 125 | } |
0 commit comments