1717package org .spockframework .compiler ;
1818
1919import org .spockframework .compiler .model .*;
20+ import org .spockframework .runtime .extension .ExtensionAnnotation ;
21+ import org .spockframework .runtime .extension .RepeatedExtensionAnnotations ;
2022import org .spockframework .runtime .model .*;
2123
2224import java .io .File ;
25+ import java .lang .annotation .Repeatable ;
26+ import java .util .List ;
27+ import java .util .Map ;
28+ import java .util .stream .Collectors ;
29+ import java .util .stream .Stream ;
2330
2431import org .codehaus .groovy .ast .*;
2532import org .codehaus .groovy .ast .expr .*;
2633
34+ import static java .util .stream .Collectors .*;
35+ import static org .spockframework .compiler .AstUtil .*;
36+ import static org .spockframework .util .ObjectUtil .asInstance ;
37+
2738/**
2839 * Puts all spec information required at runtime into annotations
2940 * attached to class members.
@@ -41,6 +52,7 @@ public SpecAnnotator(AstNodeCache nodeCache) {
4152 @ Override
4253 public void visitSpec (Spec spec ) throws Exception {
4354 addSpecMetadata (spec );
55+ addRepeatedExtensionAnnotations (spec .getAst ());
4456 }
4557
4658 private void addSpecMetadata (Spec spec ) {
@@ -55,6 +67,7 @@ private void addSpecMetadata(Spec spec) {
5567 @ Override
5668 public void visitField (Field field ) throws Exception {
5769 addFieldMetadata (field );
70+ addRepeatedExtensionAnnotations (field .getAst ());
5871 }
5972
6073 private void addFieldMetadata (Field field ) {
@@ -70,6 +83,103 @@ private void addFieldMetadata(Field field) {
7083 public void visitMethod (Method method ) throws Exception {
7184 if (method instanceof FeatureMethod )
7285 addFeatureMetadata ((FeatureMethod )method );
86+ if ((method instanceof FeatureMethod ) || (method instanceof FixtureMethod ))
87+ addRepeatedExtensionAnnotations (method .getAst ());
88+ }
89+
90+ private void addRepeatedExtensionAnnotations (AnnotatedNode annotatedNode ) {
91+ ListExpression repeatedExtensionAnnotations = annotatedNode
92+ // get all repeatable extension annotations flattened
93+ .getAnnotations ()
94+ .stream ()
95+ .flatMap (this ::flattenRepeatableExtensionAnnotationContainer )
96+ .filter (this ::isRepeatableExtensionAnnotation )
97+ // find the annotations that occur multiple times
98+ .collect (groupingBy (AnnotationNode ::getClassNode ))
99+ .entrySet ()
100+ .stream ()
101+ .filter (entry -> entry .getValue ().size () > 1 )
102+ // put them to a ListExpression
103+ .map (Map .Entry ::getKey )
104+ .map (ClassExpression ::new )
105+ .collect (collectingAndThen (Collectors .<Expression >toList (), ListExpression ::new ));
106+
107+ // if any were found, put them to a RepeatedExtensionAnnotations annotation as runtime hint
108+ if (repeatedExtensionAnnotations .getExpressions ().size () != 0 ) {
109+ AnnotationNode ann = new AnnotationNode (nodeCache .RepeatedExtensionAnnotations );
110+ ann .setMember (RepeatedExtensionAnnotations .VALUE , repeatedExtensionAnnotations );
111+ annotatedNode .addAnnotation (ann );
112+ }
113+ }
114+
115+ private Stream <? extends AnnotationNode > flattenRepeatableExtensionAnnotationContainer (AnnotationNode container ) {
116+ // supply the container itself, it could also be a valid extension annotation
117+ Stream <? extends AnnotationNode > result = Stream .of (container );
118+
119+ Expression value = container .getMember ("value" );
120+ if (value instanceof ListExpression ) {
121+ List <Expression > valueExpressions = asInstance (value , ListExpression .class ).getExpressions ();
122+ switch (valueExpressions .size ()) {
123+ case 0 :
124+ // no value, nothing to do
125+ break ;
126+
127+ case 1 :
128+ result = handleSingleContainedAnnotation (container , result , valueExpressions .get (0 ));
129+ break ;
130+
131+ default :
132+ result = handleMultipleContainedAnnotations (container , result , valueExpressions );
133+ break ;
134+ }
135+ } else if (value instanceof AnnotationConstantExpression ) {
136+ result = handleSingleContainedAnnotation (container , result , value );
137+ }
138+
139+ return result ;
140+ }
141+
142+ private Stream <? extends AnnotationNode > handleSingleContainedAnnotation (AnnotationNode container ,
143+ Stream <? extends AnnotationNode > result ,
144+ Expression value ) {
145+ if (notRepeatedAnnotation (value , container )) {
146+ return result ;
147+ }
148+
149+ AnnotationNode annotationNode = asInstance (
150+ asInstance (value , AnnotationConstantExpression .class ).getValue (),
151+ AnnotationNode .class );
152+ // supply annotation twice in case there is only the container annotation
153+ // with one contained annotation and no annotation besides it
154+ // in that case we also need the RepeatedExtensionAnnotations at runtime
155+ // to unwrap the container
156+ return Stream .concat (result , Stream .of (annotationNode , annotationNode ));
157+ }
158+
159+ private Stream <? extends AnnotationNode > handleMultipleContainedAnnotations (AnnotationNode container ,
160+ Stream <? extends AnnotationNode > result ,
161+ List <Expression > valueExpressions ) {
162+ if (notRepeatedAnnotation (valueExpressions .get (0 ), container )) {
163+ return result ;
164+ }
165+
166+ return Stream .concat (result , valueExpressions
167+ .stream ()
168+ .map (AnnotationConstantExpression .class ::cast )
169+ .map (AnnotationConstantExpression ::getValue )
170+ .map (AnnotationNode .class ::cast ));
171+ }
172+
173+ private boolean notRepeatedAnnotation (Expression clazz , AnnotationNode container ) {
174+ AnnotationNode repeatableAnnotation = getAnnotation (clazz .getType (), Repeatable .class );
175+ return (repeatableAnnotation == null ) ||
176+ !repeatableAnnotation .getMember ("value" ).getType ().equals (container .getClassNode ());
177+ }
178+
179+ private boolean isRepeatableExtensionAnnotation (AnnotationNode annotation ) {
180+ ClassNode annotationClass = annotation .getClassNode ();
181+ return hasAnnotation (annotationClass , ExtensionAnnotation .class ) &&
182+ hasAnnotation (annotationClass , Repeatable .class );
73183 }
74184
75185 private void addFeatureMetadata (FeatureMethod feature ) {
0 commit comments