Skip to content

Commit 1b3efb1

Browse files
authored
Merge pull request #994 from rabbitmq/refactor-duplicate
Do not use reflection to duplicate ClientParameters and consumer builder
2 parents 168ba5c + 95d3121 commit 1b3efb1

4 files changed

Lines changed: 131 additions & 33 deletions

File tree

src/main/java/com/rabbitmq/stream/impl/Client.java

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@
115115
import io.netty.util.concurrent.Future;
116116
import io.netty.util.concurrent.GenericFutureListener;
117117
import java.io.OutputStream;
118-
import java.lang.reflect.Field;
119118
import java.net.ConnectException;
120119
import java.net.InetSocketAddress;
121120
import java.net.SocketAddress;
@@ -2578,6 +2577,7 @@ public static ClientParameters maybeSetUpClientParametersFromUris(
25782577
}
25792578

25802579
public static class ClientParameters {
2580+
// IMPORTANT: When adding new fields, make sure to update the copy constructor method below!
25812581

25822582
private final Map<String, String> clientProperties = new ConcurrentHashMap<>();
25832583
EventLoopGroup eventLoopGroup;
@@ -2622,6 +2622,44 @@ public static class ClientParameters {
26222622
// for other server frames
26232623
private ExecutorServiceFactory executorServiceFactory;
26242624

2625+
public ClientParameters() {}
2626+
2627+
public ClientParameters(ClientParameters other) {
2628+
// Copy clientProperties map
2629+
this.clientProperties.putAll(other.clientProperties);
2630+
2631+
// Copy all other fields
2632+
this.eventLoopGroup = other.eventLoopGroup;
2633+
this.codec = other.codec;
2634+
this.host = other.host;
2635+
this.port = other.port;
2636+
this.compressionCodecFactory = other.compressionCodecFactory;
2637+
this.virtualHost = other.virtualHost;
2638+
this.requestedHeartbeat = other.requestedHeartbeat;
2639+
this.requestedMaxFrameSize = other.requestedMaxFrameSize;
2640+
this.publishConfirmListener = other.publishConfirmListener;
2641+
this.publishErrorListener = other.publishErrorListener;
2642+
this.chunkListener = other.chunkListener;
2643+
this.messageListener = other.messageListener;
2644+
this.messageIgnoredListener = other.messageIgnoredListener;
2645+
this.metadataListener = other.metadataListener;
2646+
this.creditNotification = other.creditNotification;
2647+
this.consumerUpdateListener = other.consumerUpdateListener;
2648+
this.shutdownListener = other.shutdownListener;
2649+
this.saslConfiguration = other.saslConfiguration;
2650+
this.credentialsProvider = other.credentialsProvider;
2651+
this.credentialsManager = other.credentialsManager;
2652+
this.chunkChecksum = other.chunkChecksum;
2653+
this.metricsCollector = other.metricsCollector;
2654+
this.sslContext = other.sslContext;
2655+
this.byteBufAllocator = other.byteBufAllocator;
2656+
this.rpcTimeout = other.rpcTimeout;
2657+
this.channelCustomizer = other.channelCustomizer;
2658+
this.bootstrapCustomizer = other.bootstrapCustomizer;
2659+
this.dispatchingExecutorServiceFactory = other.dispatchingExecutorServiceFactory;
2660+
this.executorServiceFactory = other.executorServiceFactory;
2661+
}
2662+
26252663
public ClientParameters host(String host) {
26262664
this.host = host;
26272665
return this;
@@ -2831,20 +2869,7 @@ Duration rpcTimeout() {
28312869
}
28322870

28332871
public ClientParameters duplicate() {
2834-
ClientParameters duplicate = new ClientParameters();
2835-
for (Field field : ClientParameters.class.getDeclaredFields()) {
2836-
field.setAccessible(true);
2837-
try {
2838-
Object value = field.get(this);
2839-
if (value instanceof Map) {
2840-
value = new ConcurrentHashMap<>((Map<?, ?>) value);
2841-
}
2842-
field.set(duplicate, value);
2843-
} catch (IllegalAccessException e) {
2844-
throw new StreamException("Error while duplicating client parameters", e);
2845-
}
2846-
}
2847-
return duplicate;
2872+
return new ClientParameters(this);
28482873
}
28492874
}
28502875

src/main/java/com/rabbitmq/stream/impl/StreamConsumerBuilder.java

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2020-2025 Broadcom. All Rights Reserved.
1+
// Copyright (c) 2020-2026 Broadcom. All Rights Reserved.
22
// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
33
//
44
// This software, the RabbitMQ Stream Java client library, is dual-licensed under the
@@ -25,11 +25,8 @@
2525
import com.rabbitmq.stream.MessageHandler;
2626
import com.rabbitmq.stream.OffsetSpecification;
2727
import com.rabbitmq.stream.Resource;
28-
import com.rabbitmq.stream.StreamException;
2928
import com.rabbitmq.stream.SubscriptionListener;
3029
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
31-
import java.lang.reflect.Field;
32-
import java.lang.reflect.Modifier;
3330
import java.time.Duration;
3431
import java.util.ArrayList;
3532
import java.util.Arrays;
@@ -64,6 +61,31 @@ public StreamConsumerBuilder(StreamEnvironment environment) {
6461
this.environment = environment;
6562
}
6663

64+
public StreamConsumerBuilder(StreamConsumerBuilder other) {
65+
// Copy environment reference
66+
this.environment = other.environment;
67+
68+
// Copy subscriptionProperties map
69+
this.subscriptionProperties.putAll(other.subscriptionProperties);
70+
71+
// Copy all other fields
72+
this.stream = other.stream;
73+
this.superStream = other.superStream;
74+
this.offsetSpecification = other.offsetSpecification;
75+
this.messageHandler = other.messageHandler;
76+
this.name = other.name;
77+
this.autoTrackingStrategy = other.autoTrackingStrategy;
78+
this.manualTrackingStrategy = other.manualTrackingStrategy;
79+
this.noTrackingStrategy = other.noTrackingStrategy;
80+
this.lazyInit = other.lazyInit;
81+
this.subscriptionListener = other.subscriptionListener;
82+
// Note: flowConfiguration is initialized in field declaration, copy its strategy
83+
this.flowConfiguration.strategy(other.flowConfiguration.getStrategy());
84+
this.consumerUpdateListener = other.consumerUpdateListener;
85+
this.filterConfiguration = other.filterConfiguration;
86+
this.listeners.addAll(other.listeners);
87+
}
88+
6789
@Override
6890
public ConsumerBuilder stream(String stream) {
6991
this.stream = stream;
@@ -276,19 +298,7 @@ public Consumer build() {
276298
}
277299

278300
StreamConsumerBuilder duplicate() {
279-
StreamConsumerBuilder duplicate = new StreamConsumerBuilder(this.environment);
280-
for (Field field : StreamConsumerBuilder.class.getDeclaredFields()) {
281-
if (Modifier.isStatic(field.getModifiers())) {
282-
continue;
283-
}
284-
field.setAccessible(true);
285-
try {
286-
field.set(duplicate, field.get(this));
287-
} catch (IllegalAccessException e) {
288-
throw new StreamException("Error while duplicating stream producer builder", e);
289-
}
290-
}
291-
return duplicate;
301+
return new StreamConsumerBuilder(this);
292302
}
293303

294304
static class TrackingConfiguration {
@@ -476,6 +486,10 @@ public FlowConfiguration strategy(ConsumerFlowStrategy strategy) {
476486
public ConsumerBuilder builder() {
477487
return this.consumerBuilder;
478488
}
489+
490+
private ConsumerFlowStrategy getStrategy() {
491+
return this.strategy;
492+
}
479493
}
480494

481495
// to help testing

src/test/java/com/rabbitmq/stream/impl/ClientParametersTest.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2020-2025 Broadcom. All Rights Reserved.
1+
// Copyright (c) 2020-2026 Broadcom. All Rights Reserved.
22
// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
33
//
44
// This software, the RabbitMQ Stream Java client library, is dual-licensed under the
@@ -15,8 +15,11 @@
1515
package com.rabbitmq.stream.impl;
1616

1717
import static org.assertj.core.api.Assertions.assertThat;
18+
import static org.junit.jupiter.api.Assertions.assertEquals;
1819

1920
import com.rabbitmq.stream.impl.Client.ClientParameters;
21+
import java.lang.reflect.Field;
22+
import java.lang.reflect.Modifier;
2023
import org.junit.jupiter.api.Test;
2124

2225
public class ClientParametersTest {
@@ -36,4 +39,20 @@ void duplicate() {
3639
clientParameters2.clientProperty("connection_name", "consumer");
3740
assertThat(clientParameters1.clientProperties()).containsEntry("connection_name", "producer");
3841
}
42+
43+
@Test
44+
void duplicateMethodHandlesAllFields() {
45+
Field[] allFields = ClientParameters.class.getDeclaredFields();
46+
int nonStaticFields = 0;
47+
for (Field field : allFields) {
48+
if (!Modifier.isStatic(field.getModifiers())) {
49+
nonStaticFields++;
50+
}
51+
}
52+
53+
assertEquals(
54+
30,
55+
nonStaticFields,
56+
"If this fails, update the copy constructor method to handle the new field(s)");
57+
}
3958
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) 2026 Broadcom. All Rights Reserved.
2+
// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
3+
//
4+
// This software, the RabbitMQ Stream Java client library, is dual-licensed under the
5+
// Mozilla Public License 2.0 ("MPL"), and the Apache License version 2 ("ASL").
6+
// For the MPL, please see LICENSE-MPL-RabbitMQ. For the ASL,
7+
// please see LICENSE-APACHE2.
8+
//
9+
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
10+
// either express or implied. See the LICENSE file for specific language governing
11+
// rights and limitations of this software.
12+
//
13+
// If you have any questions regarding licensing, please contact us at
14+
// info@rabbitmq.com.
15+
package com.rabbitmq.stream.impl;
16+
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
19+
import java.lang.reflect.Field;
20+
import java.lang.reflect.Modifier;
21+
import org.junit.jupiter.api.Test;
22+
23+
public class StreamConsumerBuilderTest {
24+
25+
@Test
26+
void duplicateMethodHandlesAllFields() {
27+
Field[] allFields = StreamConsumerBuilder.class.getDeclaredFields();
28+
int nonStaticFields = 0;
29+
for (Field field : allFields) {
30+
if (!Modifier.isStatic(field.getModifiers())) {
31+
nonStaticFields++;
32+
}
33+
}
34+
35+
assertEquals(
36+
16,
37+
nonStaticFields,
38+
"If this fails, update the copy constructor method to handle the new field(s)");
39+
}
40+
}

0 commit comments

Comments
 (0)