1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.mqtt;
17
18 import io.netty.util.collection.IntObjectHashMap;
19
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.ArrayList;
24
25
26
27
28 public final class MqttProperties {
29
30 public enum MqttPropertyType {
31
32 PAYLOAD_FORMAT_INDICATOR(0x01),
33 REQUEST_PROBLEM_INFORMATION(0x17),
34 REQUEST_RESPONSE_INFORMATION(0x19),
35 MAXIMUM_QOS(0x24),
36 RETAIN_AVAILABLE(0x25),
37 WILDCARD_SUBSCRIPTION_AVAILABLE(0x28),
38 SUBSCRIPTION_IDENTIFIER_AVAILABLE(0x29),
39 SHARED_SUBSCRIPTION_AVAILABLE(0x2A),
40
41
42 SERVER_KEEP_ALIVE(0x13),
43 RECEIVE_MAXIMUM(0x21),
44 TOPIC_ALIAS_MAXIMUM(0x22),
45 TOPIC_ALIAS(0x23),
46
47
48 PUBLICATION_EXPIRY_INTERVAL(0x02),
49 SESSION_EXPIRY_INTERVAL(0x11),
50 WILL_DELAY_INTERVAL(0x18),
51 MAXIMUM_PACKET_SIZE(0x27),
52
53
54 SUBSCRIPTION_IDENTIFIER(0x0B),
55
56
57 CONTENT_TYPE(0x03),
58 RESPONSE_TOPIC(0x08),
59 ASSIGNED_CLIENT_IDENTIFIER(0x12),
60 AUTHENTICATION_METHOD(0x15),
61 RESPONSE_INFORMATION(0x1A),
62 SERVER_REFERENCE(0x1C),
63 REASON_STRING(0x1F),
64 USER_PROPERTY(0x26),
65
66
67 CORRELATION_DATA(0x09),
68 AUTHENTICATION_DATA(0x16);
69
70 private static final MqttPropertyType[] VALUES;
71
72 static {
73 VALUES = new MqttPropertyType[43];
74 for (MqttPropertyType v : values()) {
75 VALUES[v.value] = v;
76 }
77 }
78
79 private final int value;
80
81 MqttPropertyType(int value) {
82 this.value = value;
83 }
84
85 public int value() {
86 return value;
87 }
88
89 public static MqttPropertyType valueOf(int type) {
90 MqttPropertyType t = null;
91 try {
92 t = VALUES[type];
93 } catch (ArrayIndexOutOfBoundsException ignored) {
94
95 }
96 if (t == null) {
97 throw new IllegalArgumentException("unknown property type: " + type);
98 }
99 return t;
100 }
101 }
102
103 public static final MqttProperties NO_PROPERTIES = new MqttProperties(false);
104
105 static MqttProperties withEmptyDefaults(MqttProperties properties) {
106 if (properties == null) {
107 return MqttProperties.NO_PROPERTIES;
108 }
109 return properties;
110 }
111
112
113
114
115
116
117 public abstract static class MqttProperty<T> {
118 final T value;
119 final int propertyId;
120
121 protected MqttProperty(int propertyId, T value) {
122 this.propertyId = propertyId;
123 this.value = value;
124 }
125
126
127
128
129
130
131 public T value() {
132 return value;
133 }
134
135
136
137
138
139 public int propertyId() {
140 return propertyId;
141 }
142
143 @Override
144 public int hashCode() {
145 return propertyId + 31 * value.hashCode();
146 }
147
148 @Override
149 public boolean equals(Object obj) {
150 if (this == obj) {
151 return true;
152 }
153 if (obj == null || getClass() != obj.getClass()) {
154 return false;
155 }
156 MqttProperty that = (MqttProperty) obj;
157 return this.propertyId == that.propertyId && this.value.equals(that.value);
158 }
159 }
160
161 public static final class IntegerProperty extends MqttProperty<Integer> {
162
163 public IntegerProperty(int propertyId, Integer value) {
164 super(propertyId, value);
165 }
166
167 @Override
168 public String toString() {
169 return "IntegerProperty(" + propertyId + ", " + value + ")";
170 }
171 }
172
173 public static final class StringProperty extends MqttProperty<String> {
174
175 public StringProperty(int propertyId, String value) {
176 super(propertyId, value);
177 }
178
179 @Override
180 public String toString() {
181 return "StringProperty(" + propertyId + ", " + value + ")";
182 }
183 }
184
185 public static final class StringPair {
186 public final String key;
187 public final String value;
188
189 public StringPair(String key, String value) {
190 this.key = key;
191 this.value = value;
192 }
193
194 @Override
195 public int hashCode() {
196 return key.hashCode() + 31 * value.hashCode();
197 }
198
199 @Override
200 public boolean equals(Object obj) {
201 if (this == obj) {
202 return true;
203 }
204 if (obj == null || getClass() != obj.getClass()) {
205 return false;
206 }
207 StringPair that = (StringPair) obj;
208
209 return that.key.equals(this.key) && that.value.equals(this.value);
210 }
211 }
212
213
214
215 public static final class UserProperties extends MqttProperty<List<StringPair>> {
216 public UserProperties() {
217 super(MqttPropertyType.USER_PROPERTY.value, new ArrayList<StringPair>());
218 }
219
220
221
222
223
224
225 public UserProperties(Collection<StringPair> values) {
226 this();
227 this.value.addAll(values);
228 }
229
230 private static UserProperties fromUserPropertyCollection(Collection<UserProperty> properties) {
231 UserProperties userProperties = new UserProperties();
232 for (UserProperty property: properties) {
233 userProperties.add(new StringPair(property.value.key, property.value.value));
234 }
235 return userProperties;
236 }
237
238 public void add(StringPair pair) {
239 this.value.add(pair);
240 }
241
242 public void add(String key, String value) {
243 this.value.add(new StringPair(key, value));
244 }
245
246 @Override
247 public String toString() {
248 StringBuilder builder = new StringBuilder("UserProperties(");
249 boolean first = true;
250 for (StringPair pair: value) {
251 if (!first) {
252 builder.append(", ");
253 }
254 builder.append(pair.key + "->" + pair.value);
255 first = false;
256 }
257 builder.append(")");
258 return builder.toString();
259 }
260 }
261
262 public static final class UserProperty extends MqttProperty<StringPair> {
263 public UserProperty(String key, String value) {
264 super(MqttPropertyType.USER_PROPERTY.value, new StringPair(key, value));
265 }
266
267 @Override
268 public String toString() {
269 return "UserProperty(" + value.key + ", " + value.value + ")";
270 }
271 }
272
273 public static final class BinaryProperty extends MqttProperty<byte[]> {
274
275 public BinaryProperty(int propertyId, byte[] value) {
276 super(propertyId, value);
277 }
278
279 @Override
280 public String toString() {
281 return "BinaryProperty(" + propertyId + ", " + value.length + " bytes)";
282 }
283 }
284
285 public MqttProperties() {
286 this(true);
287 }
288
289 private MqttProperties(boolean canModify) {
290 this.canModify = canModify;
291 }
292
293 private IntObjectHashMap<MqttProperty> props;
294 private List<UserProperty> userProperties;
295 private List<IntegerProperty> subscriptionIds;
296 private final boolean canModify;
297
298 public void add(MqttProperty property) {
299 if (!canModify) {
300 throw new UnsupportedOperationException("adding property isn't allowed");
301 }
302 IntObjectHashMap<MqttProperty> props = this.props;
303 if (property.propertyId == MqttPropertyType.USER_PROPERTY.value) {
304 List<UserProperty> userProperties = this.userProperties;
305 if (userProperties == null) {
306 userProperties = new ArrayList<UserProperty>(1);
307 this.userProperties = userProperties;
308 }
309 if (property instanceof UserProperty) {
310 userProperties.add((UserProperty) property);
311 } else if (property instanceof UserProperties) {
312 for (StringPair pair: ((UserProperties) property).value) {
313 userProperties.add(new UserProperty(pair.key, pair.value));
314 }
315 } else {
316 throw new IllegalArgumentException("User property must be of UserProperty or UserProperties type");
317 }
318 } else if (property.propertyId == MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value) {
319 List<IntegerProperty> subscriptionIds = this.subscriptionIds;
320 if (subscriptionIds == null) {
321 subscriptionIds = new ArrayList<IntegerProperty>(1);
322 this.subscriptionIds = subscriptionIds;
323 }
324 if (property instanceof IntegerProperty) {
325 subscriptionIds.add((IntegerProperty) property);
326 } else {
327 throw new IllegalArgumentException("Subscription ID must be an integer property");
328 }
329 } else {
330 if (props == null) {
331 props = new IntObjectHashMap<MqttProperty>();
332 this.props = props;
333 }
334 props.put(property.propertyId, property);
335 }
336 }
337
338 public Collection<? extends MqttProperty> listAll() {
339 IntObjectHashMap<MqttProperty> props = this.props;
340 if (props == null && subscriptionIds == null && userProperties == null) {
341 return Collections.<MqttProperty>emptyList();
342 }
343 if (subscriptionIds == null && userProperties == null) {
344 return props.values();
345 }
346 if (props == null && userProperties == null) {
347 return subscriptionIds;
348 }
349 List<MqttProperty> propValues = new ArrayList<MqttProperty>(props != null ? props.size() : 1);
350 if (props != null) {
351 propValues.addAll(props.values());
352 }
353 if (subscriptionIds != null) {
354 propValues.addAll(subscriptionIds);
355 }
356 if (userProperties != null) {
357 propValues.add(UserProperties.fromUserPropertyCollection(userProperties));
358 }
359 return propValues;
360 }
361
362 public boolean isEmpty() {
363 IntObjectHashMap<MqttProperty> props = this.props;
364 return props == null || props.isEmpty();
365 }
366
367
368
369
370
371
372
373
374 public MqttProperty getProperty(int propertyId) {
375 if (propertyId == MqttPropertyType.USER_PROPERTY.value) {
376
377 List<UserProperty> userProperties = this.userProperties;
378 if (userProperties == null) {
379 return null;
380 }
381 return UserProperties.fromUserPropertyCollection(userProperties);
382 }
383 if (propertyId == MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value) {
384 List<IntegerProperty> subscriptionIds = this.subscriptionIds;
385 if (subscriptionIds == null || subscriptionIds.isEmpty()) {
386 return null;
387 }
388 return subscriptionIds.get(0);
389 }
390 IntObjectHashMap<MqttProperty> props = this.props;
391 return props == null ? null : props.get(propertyId);
392 }
393
394
395
396
397
398
399
400
401
402 public List<? extends MqttProperty> getProperties(int propertyId) {
403 if (propertyId == MqttPropertyType.USER_PROPERTY.value) {
404 return userProperties == null ? Collections.<MqttProperty>emptyList() : userProperties;
405 }
406 if (propertyId == MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value) {
407 return subscriptionIds == null ? Collections.<MqttProperty>emptyList() : subscriptionIds;
408 }
409 IntObjectHashMap<MqttProperty> props = this.props;
410 return (props == null || !props.containsKey(propertyId)) ?
411 Collections.<MqttProperty>emptyList() :
412 Collections.singletonList(props.get(propertyId));
413 }
414 }