Skip to content

Commit d4cf8eb

Browse files
committed
Added topic tree compaction, improved TopicLevel(Iterator)
1 parent 612f1e4 commit d4cf8eb

File tree

6 files changed

+524
-205
lines changed

6 files changed

+524
-205
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* Copyright 2018 dc-square and the HiveMQ MQTT Client Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package com.hivemq.client.internal.mqtt.datatypes;
19+
20+
import com.hivemq.client.internal.util.ByteArrayUtil;
21+
import com.hivemq.client.mqtt.datatypes.MqttTopic;
22+
import com.hivemq.client.mqtt.datatypes.MqttTopicFilter;
23+
import org.jetbrains.annotations.NotNull;
24+
25+
import java.util.Arrays;
26+
import java.util.NoSuchElementException;
27+
28+
/**
29+
* Iterator for a topic or topic filter.
30+
* <p>
31+
* equals and hashCode match the current level.
32+
*
33+
* @author Silvio Giebl
34+
*/
35+
public class MqttTopicIterator extends MqttTopicLevel {
36+
37+
public static @NotNull MqttTopicIterator of(final @NotNull MqttTopicImpl topic) {
38+
final byte[] binary = topic.toBinary();
39+
return new MqttTopicIterator(binary, -1, -1, binary.length);
40+
}
41+
42+
public static @NotNull MqttTopicIterator of(final @NotNull MqttTopicFilterImpl topicFilter) {
43+
final byte[] binary = topicFilter.toBinary();
44+
final int start = topicFilter.getFilterByteStart() - 1;
45+
return new MqttTopicIterator(
46+
binary, start, start, topicFilter.containsMultiLevelWildcard() ? (binary.length - 2) : binary.length);
47+
}
48+
49+
private int start;
50+
private int end;
51+
private final int allEnd;
52+
53+
private MqttTopicIterator(final @NotNull byte[] array, final int start, final int end, final int allEnd) {
54+
super(array);
55+
this.start = start;
56+
this.end = end;
57+
this.allEnd = allEnd;
58+
}
59+
60+
@Override
61+
protected int getStart() {
62+
return start;
63+
}
64+
65+
@Override
66+
protected int getEnd() {
67+
return end;
68+
}
69+
70+
public boolean hasNext() {
71+
return end != allEnd;
72+
}
73+
74+
public boolean hasMultiLevelWildcard() {
75+
return allEnd != array.length;
76+
}
77+
78+
public @NotNull MqttTopicIterator fork() {
79+
return new MqttTopicIterator(array, start, end, allEnd);
80+
}
81+
82+
public @NotNull MqttTopicLevel next() {
83+
if (!hasNext()) {
84+
throw new NoSuchElementException();
85+
}
86+
start = end + 1;
87+
end = ByteArrayUtil.indexOf(array, start, (byte) MqttTopic.TOPIC_LEVEL_SEPARATOR);
88+
return this;
89+
}
90+
91+
@Override
92+
public @NotNull MqttTopicLevel trim() {
93+
if (!hasNext()) {
94+
return MqttTopicLevel.of(array, start, end);
95+
}
96+
final int start = this.start;
97+
final int end = this.end;
98+
this.start = this.end = allEnd;
99+
return new MqttTopicLevels(Arrays.copyOfRange(array, start, allEnd), end - start);
100+
}
101+
102+
public boolean forwardIfEqual(final @NotNull MqttTopicLevels topicLevels) {
103+
final byte[] topicLevelsArray = topicLevels.getArray();
104+
final int topicLevelsEnd = topicLevels.getEnd();
105+
final int to = end + topicLevelsArray.length - topicLevelsEnd;
106+
if ((to <= allEnd) && ByteArrayUtil.equals(array, end + 1, to, topicLevelsArray, topicLevelsEnd + 1,
107+
topicLevelsArray.length)) {
108+
start = end = to;
109+
return true;
110+
}
111+
return false;
112+
}
113+
114+
public int forwardWhileEqual(final @NotNull MqttTopicLevels levels) {
115+
if (!hasNext()) {
116+
return levels.getEnd();
117+
}
118+
int branchIndex = end;
119+
int levelsBranchIndex = levels.getEnd();
120+
int index = branchIndex + 1;
121+
int levelsIndex = levelsBranchIndex + 1;
122+
final byte[] levelsArray = levels.getArray();
123+
while (true) {
124+
final boolean isEnd = index == allEnd;
125+
final boolean isLevelsEnd = levelsIndex == levelsArray.length;
126+
if (isLevelsEnd || isEnd) {
127+
if ((isLevelsEnd || (levelsArray[levelsIndex] == MqttTopicImpl.TOPIC_LEVEL_SEPARATOR)) &&
128+
(isEnd || (array[index] == MqttTopicImpl.TOPIC_LEVEL_SEPARATOR))) {
129+
branchIndex = index;
130+
levelsBranchIndex = levelsIndex;
131+
}
132+
break;
133+
}
134+
final byte lb = levelsArray[levelsIndex];
135+
if (array[index] == lb) {
136+
if (lb == MqttTopicImpl.TOPIC_LEVEL_SEPARATOR) {
137+
branchIndex = index;
138+
levelsBranchIndex = levelsIndex;
139+
}
140+
index++;
141+
levelsIndex++;
142+
} else {
143+
break;
144+
}
145+
}
146+
start = end = branchIndex;
147+
return levelsBranchIndex;
148+
}
149+
150+
public boolean forwardIfMatch(final @NotNull MqttTopicLevels levels) {
151+
if (!hasNext()) {
152+
return false;
153+
}
154+
int index = end + 1;
155+
int levelsIndex = levels.getEnd() + 1;
156+
final byte[] levelsArray = levels.getArray();
157+
while (true) {
158+
final boolean isEnd = index == allEnd;
159+
final boolean isLevelsEnd = levelsIndex == levelsArray.length;
160+
if (isLevelsEnd) {
161+
if (isEnd || (array[index] == MqttTopicImpl.TOPIC_LEVEL_SEPARATOR)) {
162+
start = end = index;
163+
return true;
164+
}
165+
return false;
166+
}
167+
if (isEnd) {
168+
return false;
169+
}
170+
final byte lb = levelsArray[levelsIndex];
171+
if (array[index] == lb) {
172+
index++;
173+
levelsIndex++;
174+
} else if (lb == MqttTopicFilter.SINGLE_LEVEL_WILDCARD) {
175+
while ((index < allEnd) && (array[index] != MqttTopicImpl.TOPIC_LEVEL_SEPARATOR)) {
176+
index++;
177+
}
178+
levelsIndex++;
179+
} else {
180+
return false;
181+
}
182+
}
183+
}
184+
}

src/main/java/com/hivemq/client/internal/mqtt/datatypes/MqttTopicLevel.java

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,65 +18,46 @@
1818
package com.hivemq.client.internal.mqtt.datatypes;
1919

2020
import com.hivemq.client.internal.util.ByteArray;
21-
import com.hivemq.client.internal.util.ByteArrayUtil;
22-
import com.hivemq.client.mqtt.datatypes.MqttTopic;
2321
import com.hivemq.client.mqtt.datatypes.MqttTopicFilter;
2422
import org.jetbrains.annotations.NotNull;
25-
import org.jetbrains.annotations.Nullable;
2623

2724
import java.util.Arrays;
2825

2926
/**
27+
* Single topic or topic filter level. May be the single level wildcard but must not be the multi level wildcard (the
28+
* multi level wildcard does not represent a topic level).
29+
*
3030
* @author Silvio Giebl
3131
*/
32-
public class MqttTopicLevel extends ByteArray.Range {
33-
34-
public static final @NotNull ByteArray SINGLE_LEVEL_WILDCARD =
35-
new ByteArray(new byte[]{MqttTopicFilter.SINGLE_LEVEL_WILDCARD});
36-
37-
public static @NotNull MqttTopicLevel root(final @NotNull MqttTopicImpl topic) {
38-
return new MqttTopicLevel(topic.toBinary(), -1, -1);
39-
}
40-
41-
public static @NotNull MqttTopicLevel root(final @NotNull MqttTopicFilterImpl topicFilter) {
42-
final int start = topicFilter.getFilterByteStart() - 1;
43-
return new MqttTopicLevel(topicFilter.toBinary(), start, start);
44-
}
32+
public class MqttTopicLevel extends ByteArray {
4533

46-
private static int nextEnd(final @NotNull byte[] array, final int start) {
47-
final int nextSeparator = ByteArrayUtil.indexOf(array, start, (byte) MqttTopic.TOPIC_LEVEL_SEPARATOR);
48-
return (nextSeparator == -1) ? array.length : nextSeparator;
49-
}
34+
private static final @NotNull MqttTopicLevel SINGLE_LEVEL_WILDCARD =
35+
new MqttTopicLevel(new byte[]{MqttTopicFilter.SINGLE_LEVEL_WILDCARD});
5036

51-
private MqttTopicLevel(final @NotNull byte[] array, final int start, final int end) {
52-
super(array, start, end);
37+
static @NotNull MqttTopicLevel of(final @NotNull byte[] array, final int start, final int end) {
38+
if (isSingleLevelWildcard(array, start, end)) {
39+
return MqttTopicLevel.SINGLE_LEVEL_WILDCARD;
40+
}
41+
return new MqttTopicLevel(Arrays.copyOfRange(array, start, end));
5342
}
5443

55-
public @Nullable MqttTopicLevel next() {
56-
if (end == array.length) {
57-
return null;
58-
}
59-
start = end + 1;
60-
end = nextEnd(array, start);
61-
return this;
44+
private static boolean isSingleLevelWildcard(final @NotNull byte[] array, final int start, final int end) {
45+
return ((end - start) == 1) && (array[start] == MqttTopicFilter.SINGLE_LEVEL_WILDCARD);
6246
}
6347

64-
public @NotNull ByteArray copy() {
65-
if (isSingleLevelWildcard()) {
66-
return SINGLE_LEVEL_WILDCARD;
67-
}
68-
return new ByteArray(Arrays.copyOfRange(array, start, end));
48+
MqttTopicLevel(final @NotNull byte[] array) {
49+
super(array);
6950
}
7051

71-
public @NotNull MqttTopicLevel fork() {
72-
return new MqttTopicLevel(array, start, end);
52+
@NotNull byte[] getArray() {
53+
return array;
7354
}
7455

7556
public boolean isSingleLevelWildcard() {
76-
return (length() == 1) && (array[start] == MqttTopicFilter.SINGLE_LEVEL_WILDCARD);
57+
return isSingleLevelWildcard(array, getStart(), getEnd());
7758
}
7859

79-
public boolean isMultiLevelWildcard() {
80-
return (length() == 1) && (array[start] == MqttTopicFilter.MULTI_LEVEL_WILDCARD);
60+
public @NotNull MqttTopicLevel trim() {
61+
return this;
8162
}
8263
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2018 dc-square and the HiveMQ MQTT Client Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package com.hivemq.client.internal.mqtt.datatypes;
19+
20+
import com.hivemq.client.internal.util.ByteArrayUtil;
21+
import com.hivemq.client.mqtt.datatypes.MqttTopic;
22+
import org.jetbrains.annotations.NotNull;
23+
24+
import java.util.Arrays;
25+
26+
/**
27+
* Multiple (more than 1) topic or topic filter levels. May contain single level wildcard(s) but must not contain the
28+
* multi level wildcard (the multi level wildcard does not represent a topic level).
29+
* <p>
30+
* equals and hashCode match the first level.
31+
*
32+
* @author Silvio Giebl
33+
*/
34+
public class MqttTopicLevels extends MqttTopicLevel {
35+
36+
public static @NotNull MqttTopicLevels concat(
37+
final @NotNull MqttTopicLevel level1, final @NotNull MqttTopicLevel level2) {
38+
39+
final byte[] array1 = level1.trim().getArray();
40+
final byte[] array2 = level2.trim().getArray();
41+
final byte[] array = new byte[array1.length + 1 + array2.length];
42+
System.arraycopy(array1, 0, array, 0, array1.length);
43+
array[array1.length] = MqttTopic.TOPIC_LEVEL_SEPARATOR;
44+
System.arraycopy(array2, 0, array, array1.length + 1, array2.length);
45+
return new MqttTopicLevels(array, level1.length());
46+
}
47+
48+
private final int firstEnd;
49+
50+
MqttTopicLevels(final @NotNull byte[] array, final int firstEnd) {
51+
super(array);
52+
this.firstEnd = firstEnd;
53+
}
54+
55+
@Override
56+
protected int getEnd() {
57+
return firstEnd;
58+
}
59+
60+
public @NotNull MqttTopicLevel before(final int index) {
61+
if (index == array.length) {
62+
return this;
63+
}
64+
assert array[index] == MqttTopic.TOPIC_LEVEL_SEPARATOR;
65+
if (index == firstEnd) {
66+
return MqttTopicLevel.of(array, 0, firstEnd);
67+
}
68+
return new MqttTopicLevels(Arrays.copyOfRange(array, 0, index), firstEnd);
69+
}
70+
71+
public @NotNull MqttTopicLevel after(final int index) {
72+
assert array[index] == MqttTopic.TOPIC_LEVEL_SEPARATOR;
73+
final int start = index + 1;
74+
final int end = ByteArrayUtil.indexOf(array, start, (byte) MqttTopic.TOPIC_LEVEL_SEPARATOR);
75+
if (end == array.length) {
76+
return MqttTopicLevel.of(array, start, array.length);
77+
}
78+
return new MqttTopicLevels(Arrays.copyOfRange(array, start, array.length), end - start);
79+
}
80+
}

0 commit comments

Comments
 (0)