Skip to content

Commit f8cb3d6

Browse files
committed
Add check for cycles in producer requirements, includes a test to check the behavior.
1 parent 9d5c074 commit f8cb3d6

2 files changed

Lines changed: 129 additions & 12 deletions

File tree

src/main/java/nl/rug/jbi/jsm/core/pipeline/Pipeline.java

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@
1818
import java.lang.reflect.InvocationTargetException;
1919
import java.lang.reflect.Method;
2020
import java.lang.reflect.Modifier;
21-
import java.util.Collections;
22-
import java.util.List;
23-
import java.util.Map;
24-
import java.util.Set;
21+
import java.util.*;
2522

2623
import static com.google.common.base.Preconditions.checkArgument;
2724
import static com.google.common.base.Preconditions.checkNotNull;
@@ -77,8 +74,11 @@ public static void registerNewBaseData(final Class dataType) {
7774
//NOOP
7875
}
7976

80-
private Pair<Class, HandlerExecutor> processMethod(final Method listener, final BaseMetric metric)
81-
throws MetricPreparationException {
77+
private Pair<Class, HandlerExecutor> processMethod(
78+
final Method listener,
79+
final BaseMetric metric,
80+
final Stack<Class> includeStack
81+
) throws MetricPreparationException {
8282
final Class<?>[] params = listener.getParameterTypes();
8383

8484
//Listener methods always have 2 parameters
@@ -106,7 +106,10 @@ private Pair<Class, HandlerExecutor> processMethod(final Method listener, final
106106
}
107107

108108
if (listener.isAnnotationPresent(UsingProducer.class)) {
109-
final ProducerMetric producer = loadProducer(listener.getAnnotation(UsingProducer.class).value());
109+
final ProducerMetric producer = loadProducer(
110+
listener.getAnnotation(UsingProducer.class).value(),
111+
includeStack
112+
);
110113

111114
//Ensure the producer produces for the current scope
112115
if (!producer.getProduceScope().equals(metric.getScope())) {
@@ -131,7 +134,13 @@ private Pair<Class, HandlerExecutor> processMethod(final Method listener, final
131134
return new Pair<Class, HandlerExecutor>(params[1], new HandlerExecutor(listener, metric));
132135
}
133136

134-
private void registerMetricInternal(final BaseMetric metric) throws MetricPreparationException {
137+
private void registerMetricInternal(final BaseMetric metric, final Stack<Class> includeStack)
138+
throws MetricPreparationException {
139+
if (includeStack.contains(metric.getClass())) {
140+
throw new MetricPreparationException(String.format("Cyclic producer requirements: %s", includeStack));
141+
}
142+
includeStack.push(metric.getClass());
143+
135144
final List<Pair<Class, HandlerExecutor>> executors = Lists.newLinkedList();
136145

137146
for (final MetricScope resultScope : metric.getResultScopes()) {
@@ -143,7 +152,7 @@ private void registerMetricInternal(final BaseMetric metric) throws MetricPrepar
143152

144153
for (final Method m : metric.getClass().getDeclaredMethods()) {
145154
if (m.isAnnotationPresent(Subscribe.class)) {
146-
executors.add(processMethod(m, metric));
155+
executors.add(processMethod(m, metric, includeStack));
147156
}
148157
}
149158

@@ -181,6 +190,8 @@ public Class apply(Pair<Class, HandlerExecutor> classHandlerExecutorPair) {
181190
"Unable to resolve data for metric, the required data might not have the same MetricScope"
182191
);
183192
}
193+
194+
includeStack.pop();
184195
}
185196

186197
/**
@@ -196,7 +207,7 @@ public void registerMetric(final BaseMetric metric) throws MetricPreparationExce
196207
if (!(metric instanceof IsolatedMetric || metric instanceof SharedMetric)) {
197208
throw new MetricPreparationException("A metric needs to be a subclass of IsolatedMetric or SharedMetric");
198209
} else {
199-
this.registerMetricInternal(metric);
210+
this.registerMetricInternal(metric, new Stack<Class>());
200211
}
201212
}
202213

@@ -222,14 +233,17 @@ public String toString() {
222233
.toString();
223234
}
224235

225-
private ProducerMetric loadProducer(final Class<? extends ProducerMetric> producerClass) throws MetricPreparationException {
236+
private ProducerMetric loadProducer(
237+
final Class<? extends ProducerMetric> producerClass,
238+
final Stack<Class> includeStack
239+
) throws MetricPreparationException {
226240
if (this.registeredProducers.containsKey(producerClass)) {
227241
return this.registeredProducers.get(producerClass);
228242
} else {
229243
logger.debug("Creating new producer: {}", producerClass);
230244
try {
231245
final ProducerMetric pm = producerClass.getConstructor().newInstance();
232-
this.registerMetricInternal(pm);
246+
this.registerMetricInternal(pm, includeStack);
233247
this.addProducer(pm);
234248
return pm;
235249
} catch (InvocationTargetException e) {

src/test/java/nl/rug/jbi/jsm/core/MetricValidationTest.java

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,40 @@ public List<MetricResult> getResults(Map<String, MetricState> states, int invali
153153
});
154154
}
155155

156+
@Test(expected = MetricPreparationException.class)
157+
public void testCyclicProducers() throws MetricPreparationException {
158+
//CyclicProducer1 includes CyclicProducer2, which include CyclicProducer1 again
159+
this.core.registerMetric(new SharedMetric(MetricScope.CLASS) {
160+
@Subscribe
161+
@UsingProducer(CyclicProducer1.class)
162+
public void handleData(final MetricState state, final Integer one) {
163+
164+
}
165+
166+
@Override
167+
public List<MetricResult> getResults(Map<String, MetricState> states, int invalidMembers) {
168+
throw new UnsupportedOperationException();
169+
}
170+
});
171+
}
172+
173+
@Test(expected = MetricPreparationException.class)
174+
public void testUsingSelf() throws MetricPreparationException {
175+
//Producer using itself, creates implicit cyclic requirement.
176+
this.core.registerMetric(new SharedMetric(MetricScope.CLASS) {
177+
@Subscribe
178+
@UsingProducer(CyclicSelf.class)
179+
public void handleData(final MetricState state, final Integer one) {
180+
181+
}
182+
183+
@Override
184+
public List<MetricResult> getResults(Map<String, MetricState> states, int invalidMembers) {
185+
throw new UnsupportedOperationException();
186+
}
187+
});
188+
}
189+
156190
public static class TestProducer extends ProducerMetric {
157191

158192
public TestProducer() {
@@ -169,4 +203,73 @@ public Class getProducedClass() {
169203
return Object.class;
170204
}
171205
}
206+
207+
public static class CyclicProducer1 extends ProducerMetric {
208+
209+
public CyclicProducer1() {
210+
super(MetricScope.CLASS, MetricScope.CLASS);
211+
}
212+
213+
@Subscribe
214+
@UsingProducer(CyclicProducer2.class)
215+
public void noOp(final MetricState state, final Object o) {
216+
throw new UnsupportedOperationException();
217+
}
218+
219+
@Override
220+
public List<Produce> getProduce(Map<String, MetricState> states, int invalidMembers) {
221+
throw new UnsupportedOperationException();
222+
}
223+
224+
@Override
225+
public Class getProducedClass() {
226+
return Object.class;
227+
}
228+
}
229+
230+
public static class CyclicProducer2 extends ProducerMetric {
231+
232+
public CyclicProducer2() {
233+
super(MetricScope.CLASS, MetricScope.CLASS);
234+
}
235+
236+
@Subscribe
237+
@UsingProducer(CyclicProducer1.class)
238+
public void noOp(final MetricState state, final Object o) {
239+
throw new UnsupportedOperationException();
240+
}
241+
242+
@Override
243+
public List<Produce> getProduce(Map<String, MetricState> states, int invalidMembers) {
244+
throw new UnsupportedOperationException();
245+
}
246+
247+
@Override
248+
public Class getProducedClass() {
249+
return Object.class;
250+
}
251+
}
252+
253+
public static class CyclicSelf extends ProducerMetric {
254+
255+
public CyclicSelf() {
256+
super(MetricScope.CLASS, MetricScope.CLASS);
257+
}
258+
259+
@Subscribe
260+
@UsingProducer(CyclicSelf.class)
261+
public void noOp(final MetricState state, final Object o) {
262+
throw new UnsupportedOperationException();
263+
}
264+
265+
@Override
266+
public List<Produce> getProduce(Map<String, MetricState> states, int invalidMembers) {
267+
throw new UnsupportedOperationException();
268+
}
269+
270+
@Override
271+
public Class getProducedClass() {
272+
return Object.class;
273+
}
274+
}
172275
}

0 commit comments

Comments
 (0)