Skip to content

Commit e616b29

Browse files
committed
support for placing @blockchain parameters in any position of the
original helper class method, by propagating the original argument position to the runtime (via a new annotation)
1 parent 48d5a43 commit e616b29

9 files changed

Lines changed: 167 additions & 51 deletions

File tree

flapi-runtime/src/main/java/unquietcode/tools/flapi/runtime/BlockInvocationHandler.java

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,12 @@
1616

1717
package unquietcode.tools.flapi.runtime;
1818

19+
import java.lang.annotation.Annotation;
1920
import java.lang.reflect.InvocationHandler;
2021
import java.lang.reflect.InvocationTargetException;
2122
import java.lang.reflect.Method;
2223
import java.lang.reflect.Proxy;
23-
import java.util.Arrays;
24-
import java.util.Collections;
25-
import java.util.IdentityHashMap;
26-
import java.util.Set;
24+
import java.util.*;
2725
import java.util.concurrent.atomic.AtomicReference;
2826

2927
/**
@@ -83,25 +81,49 @@ public final Object invoke(Object proxy, Method method, Object[] args) throws Th
8381
return invokeAndReturn(method, args, proxy, info);
8482
}
8583

84+
// handles legacy concerns before the chainInfo() array existed by
85+
// synthesizing a new array from the older values
8686
private Object invokeAndReturn(Method method, Object[] originalArgs, Object proxy, MethodInfo info) {
87-
final int depth = info.chain().length;
87+
final ChainInfo[] chain;
88+
89+
// true in v0.5 and before
90+
if (info.chain().length > info.chainInfo().length) {
91+
List<ChainInfo> chainInfo = new ArrayList<ChainInfo>();
92+
int position = originalArgs.length;
93+
94+
for (Class<?> type : info.chain()) {
95+
chainInfo.add(new ChainInfoHolder(position++, type));
96+
}
97+
98+
chain = chainInfo.toArray(new ChainInfo[chainInfo.size()]);
99+
} else {
100+
chain = info.chainInfo();
101+
}
102+
103+
return invokeAndReturn(method, originalArgs, proxy, info, chain);
104+
}
105+
106+
private Object invokeAndReturn(Method method, Object[] originalArgs, Object proxy, MethodInfo info, ChainInfo[] chain) {
107+
// Don't use info.chainInfo() directly, since they might not match due
108+
// to legacy considerations (will be removed in 1.0).
109+
110+
final int depth = chain.length;
88111

89112
// create the new arguments and types arrays
90-
Object[] newArgs = new Object[originalArgs.length+depth];
91113
Class<?>[] originalTypes = method.getParameterTypes();
92-
Class[] newTypes = new Class[originalTypes.length+depth];
114+
List<Class<?>> newTypes = new ArrayList<Class<?>>(Arrays.asList(originalTypes));
115+
List<Object> newArgs = new ArrayList<Object>(Arrays.asList(originalArgs));
93116

94-
for (int i=0; i < depth; ++i) {
95-
newArgs[originalArgs.length+i] = new AtomicReference();
96-
newTypes[originalTypes.length+i] = AtomicReference.class;
117+
for (ChainInfo chainInfo : chain) {
118+
newArgs.add(chainInfo.position(), new AtomicReference());
119+
newTypes.add(chainInfo.position(), AtomicReference.class);
97120
}
98121

99-
System.arraycopy(originalArgs, 0, newArgs, 0, originalArgs.length);
100-
System.arraycopy(originalTypes, 0, newTypes, 0, originalTypes.length);
101122
Method helperMethod;
102123

103124
// find the helper method
104-
helperMethod = SpringMethodUtils.findMethod(helper.getClass(), method.getName(), newTypes);
125+
final Class<?>[] newTypesArray = newTypes.toArray(new Class[newTypes.size()]);
126+
helperMethod = SpringMethodUtils.findMethod(helper.getClass(), method.getName(), newTypesArray);
105127

106128
if (helperMethod == null) {
107129
throw new IllegalStateException("unable to locate method '"+method.getName()+"' on helper");
@@ -112,10 +134,12 @@ private Object invokeAndReturn(Method method, Object[] originalArgs, Object prox
112134
helperMethod.setAccessible(true);
113135
}
114136

137+
final Object[] newArgsArray = newArgs.toArray(new Object[newArgs.size()]);
138+
115139
// invoke method
116140
Object result;
117141
try {
118-
result = helperMethod.invoke(helper, newArgs);
142+
result = helperMethod.invoke(helper, newArgsArray);
119143
} catch (IllegalAccessException ex) {
120144
throw new IllegalStateException(ex);
121145
} catch (InvocationTargetException ex) {
@@ -129,17 +153,18 @@ private Object invokeAndReturn(Method method, Object[] originalArgs, Object prox
129153
Object _returnValue = computeReturnValue(method, proxy, info, depth, helperMethod, result);
130154

131155
// unwrap helper results
132-
for (int i=depth-1; i >= 0; --i) {
133-
AtomicReference wrapper = (AtomicReference) newArgs[originalArgs.length+i];
156+
for (int i = chain.length-1; i >= 0; --i) {
157+
ChainInfo chainInfo = chain[i];
158+
AtomicReference wrapper = (AtomicReference) newArgs.get(chainInfo.position());
134159

135160
if (wrapper.get() == null) {
136-
throw new IllegalStateException("null helper provided for method "+method.getName());
161+
throw new IllegalStateException("null helper provided for method " + method.getName());
137162
}
138163

139164
BlockInvocationHandler handler
140165
= new BlockInvocationHandler(wrapper.get(), _returnValue, listeners);
141166

142-
_returnValue = handler._proxy(info.chain()[i]);
167+
_returnValue = handler._proxy(chainInfo.type());
143168
}
144169

145170
return _returnValue;
@@ -200,4 +225,29 @@ private class Tracker extends TrackingExecutionListener {
200225
}
201226
}
202227
}
203-
}
228+
229+
private static class ChainInfoHolder implements ChainInfo {
230+
private final int position;
231+
private final Class<?> type;
232+
233+
public ChainInfoHolder(int position, Class<?> type) {
234+
this.position = position;
235+
this.type = type;
236+
}
237+
238+
@Override
239+
public Class<?> type() {
240+
return type;
241+
}
242+
243+
@Override
244+
public int position() {
245+
return position;
246+
}
247+
248+
@Override
249+
public Class<? extends Annotation> annotationType() {
250+
return ChainInfo.class;
251+
}
252+
}
253+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*********************************************************************
2+
Copyright 2014 the Flapi authors
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+
package unquietcode.tools.flapi.runtime;
18+
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
24+
/**
25+
* Information about the position and type of a block
26+
* chain parameter.
27+
*
28+
* @author Ben Fagin
29+
* @version 2014-11-02
30+
*/
31+
@Target(ElementType.ANNOTATION_TYPE)
32+
@Retention(RetentionPolicy.RUNTIME)
33+
public @interface ChainInfo {
34+
Class<?> type();
35+
int position();
36+
}

flapi-runtime/src/main/java/unquietcode/tools/flapi/runtime/MethodInfo.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
@Target(ElementType.METHOD)
3030
public @interface MethodInfo {
3131
TransitionType type();
32-
Class<?>[] chain() default {};
32+
33+
@Deprecated Class<?>[] chain() default {};
34+
ChainInfo[] chainInfo() default {};
35+
3336
Class<?> next() default MethodInfo.class;
34-
}
37+
}

src/main/java/unquietcode/tools/flapi/IntrospectorSupport.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package unquietcode.tools.flapi;
1818

19+
import unquietcode.tools.flapi.annotations.BlockChain;
20+
21+
import java.lang.annotation.Annotation;
1922
import java.lang.reflect.Method;
2023

2124
import static com.google.common.base.Preconditions.checkState;
@@ -26,6 +29,21 @@
2629
*/
2730
public class IntrospectorSupport {
2831

32+
protected static <T extends Annotation> T getParameterAnnotation(Method method, int parameterIndex, Class<T> annotationClass) {
33+
for (Annotation annotation : method.getParameterAnnotations()[parameterIndex]) {
34+
if (annotation.annotationType() == annotationClass) {
35+
@SuppressWarnings("unchecked") T annotation1 = (T) annotation;
36+
return annotation1;
37+
}
38+
}
39+
40+
return null;
41+
}
42+
43+
protected static <T extends Annotation> boolean hasParameterAnnotation(Method method, int parameterIndex, Class<T> annotationClass) {
44+
return getParameterAnnotation(method, parameterIndex, annotationClass) != null;
45+
}
46+
2947
protected static String getMethodSignature(Method method) {
3048
return getMethodSignature(method, null);
3149
}
@@ -36,13 +54,21 @@ protected static String getMethodSignature(final Method method, final String nor
3654
String name = normalizedName != null ? normalizedName : method.getName();
3755
signature.append(name).append("(");
3856

57+
boolean first = true;
3958
Class<?>[] parameterTypes = method.getParameterTypes();
4059

4160
for (int i=0; i < parameterTypes.length; i++) {
4261
Class<?> parameterType = parameterTypes[i];
4362

44-
if (i > 0) {
63+
// skip BlockChain parameters
64+
if (hasParameterAnnotation(method, i, BlockChain.class)) {
65+
continue;
66+
}
67+
68+
if (!first) {
4569
signature.append(", ");
70+
} else {
71+
first = false;
4672
}
4773

4874
final String typeName;
@@ -69,4 +95,4 @@ else if (parameterType.isArray()) {
6995
signature.append(")");
7096
return signature.toString();
7197
}
72-
}
98+
}

src/main/java/unquietcode/tools/flapi/annotations/AnnotationIntrospector.java

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -231,14 +231,11 @@ private void handleMethod(MethodOutline methodOutline, Method method) {
231231
}
232232

233233
// block chaining
234-
boolean weAreAtTheEnd = false;
235-
236234
for (int i=0; i < method.getParameterTypes().length; ++i) {
237235
Class<?> parameterType = method.getParameterTypes()[i];
238236
BlockChain blockChain = getParameterAnnotation(method, i, BlockChain.class);
239237

240238
if (blockChain != null) {
241-
weAreAtTheEnd = true;
242239

243240
// check type
244241
if (parameterType != AtomicReference.class) {
@@ -247,17 +244,7 @@ private void handleMethod(MethodOutline methodOutline, Method method) {
247244

248245
BlockOutline blockOutline = handleClass(blockChain.value());
249246
methodOutline.getBlockChain().add(blockOutline);
250-
// methodOutline.getChainParameterPositions().add(i);
251-
}
252-
253-
else {
254-
255-
// We can't currently support block helpers anywhere but the final position
256-
if (weAreAtTheEnd) {
257-
throw new DescriptorBuilderException(
258-
"@BlockChain parameters are currently only supported as the last arguments to the method"
259-
);
260-
}
247+
methodOutline.getChainParameterPositions().add(i);
261248
}
262249
}
263250
}
@@ -273,17 +260,6 @@ private static void handleMethodAnnotation(MethodOutline methodOutline, Annotati
273260
helper.finish();
274261
}
275262

276-
private static <T extends Annotation> T getParameterAnnotation(Method method, int parameterIndex, Class<T> annotationClass) {
277-
for (Annotation annotation : method.getParameterAnnotations()[parameterIndex]) {
278-
if (annotation.annotationType() == annotationClass) {
279-
@SuppressWarnings("unchecked") T annotation1 = (T) annotation;
280-
return annotation1;
281-
}
282-
}
283-
284-
return null;
285-
}
286-
287263
private static <
288264
_MarkerAnnotation extends Annotation
289265
>

src/main/java/unquietcode/tools/flapi/graph/GraphBuilder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ private Set<MethodOutline> addTransition(
217217
}
218218

219219
transition.setMethodInfo(((MethodInfo) method).copy());
220+
transition.getChainParameterPositions().addAll(method.getChainParameterPositions());
220221
state.addTransitions(transition);
221222

222223
// state chain

src/main/java/unquietcode/tools/flapi/graph/components/Transition.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
*/
3131
public abstract class Transition implements Comparable<Transition> {
3232
private final List<StateClass> stateChain = new ArrayList<StateClass>();
33+
private final List<Integer> chainParameterPositions = new ArrayList<Integer>();
3334
private final TransitionType type;
3435
private MethodInfo methodInfo = new MethodInfo();
3536
private StateClass owner;
@@ -71,6 +72,10 @@ public String getMethodSignature() {
7172
return methodInfo.getMethodSignature();
7273
}
7374

75+
public List<Integer> getChainParameterPositions() {
76+
return chainParameterPositions;
77+
}
78+
7479
public MethodInfo info() {
7580
return methodInfo;
7681
}

src/main/java/unquietcode/tools/flapi/graph/processors/GraphProcessor.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
import unquietcode.tools.flapi.graph.components.StateClass;
2727
import unquietcode.tools.flapi.graph.components.TerminalTransition;
2828
import unquietcode.tools.flapi.graph.components.Transition;
29+
import unquietcode.tools.flapi.runtime.ChainInfo;
2930
import unquietcode.tools.flapi.runtime.MethodInfo;
3031
import unquietcode.tools.flapi.runtime.Tracked;
3132

3233
import java.util.Collections;
3334
import java.util.IdentityHashMap;
35+
import java.util.List;
3436
import java.util.Set;
3537
import java.util.concurrent.atomic.AtomicReference;
3638

@@ -79,11 +81,23 @@ public void visit(LateralTransition transition) {
7981

8082
// store the type information for the state chain
8183
if (!transition.getStateChain().isEmpty()) {
82-
JAnnotationArrayMember chain = infoAnnotation.paramArray("chain");
84+
List<Integer> chainParameterPositions = transition.getChainParameterPositions();
85+
int positionCounter = 0;
8386

84-
for (StateClass sc : transition.getStateChain()) {
87+
List<StateClass> stateChain = transition.getStateChain();
88+
JAnnotationArrayMember chain = infoAnnotation.paramArray("chainInfo");
89+
90+
for (int i=0; i < stateChain.size(); i++) {
91+
StateClass sc = stateChain.get(i);
8592
JClass type = BUILDER_OR_WRAPPER_INTERFACE_STRATEGY.createWeakType(ctx, sc);
86-
chain.param(type);
93+
94+
int position = chainParameterPositions.isEmpty()
95+
? positionCounter++
96+
: chainParameterPositions.get(i);
97+
98+
JAnnotationUse chainInfo = chain.annotate(ChainInfo.class);
99+
chainInfo.param("type", type);
100+
chainInfo.param("position", position);
87101
}
88102
}
89103

src/main/java/unquietcode/tools/flapi/outline/MethodOutline.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
*/
2727
public class MethodOutline extends MethodInfo implements Outline {
2828
private final List<BlockOutline> blockChain = new ArrayList<BlockOutline>();
29+
private final List<Integer> chainParameterPositions = new ArrayList<Integer>();
2930
private boolean isTerminal = false;
3031
private String returnType;
3132
private Integer group;
@@ -43,6 +44,10 @@ public List<BlockOutline> getBlockChain() {
4344
return blockChain;
4445
}
4546

47+
public List<Integer> getChainParameterPositions() {
48+
return chainParameterPositions;
49+
}
50+
4651
/*
4752
A terminal method will return out of the class, though
4853
may go through a block chain first.

0 commit comments

Comments
 (0)