1616package org .scribble .protocol .projection .impl ;
1717
1818import java .text .MessageFormat ;
19+ import java .util .Collections ;
1920import java .util .logging .Logger ;
2021
2122import org .scribble .common .logging .Journal ;
23+ import org .scribble .protocol .model .Activity ;
2224import org .scribble .protocol .model .Block ;
2325import org .scribble .protocol .model .Choice ;
24- import org .scribble .protocol .model .MessageSignature ;
2526import org .scribble .protocol .model .ModelObject ;
2627import org .scribble .protocol .model .Role ;
2728import org .scribble .protocol .util .InteractionUtil ;
3233 */
3334public class ChoiceProjectorRule implements ProjectorRule {
3435
36+ public static final String ALLOW_OPTIONAL = "scribble.choice.allowOptional" ;
37+
3538 private static final Logger LOG =Logger .getLogger (ChoiceProjectorRule .class .getName ());
3639
3740 /**
@@ -59,11 +62,7 @@ public boolean isSupported(ModelObject obj) {
5962 public Object project (ProjectorContext context , ModelObject model ,
6063 Role role , Journal l ) {
6164 Choice projected =new Choice ();
62- Object ret =projected ;
6365 Choice source =(Choice )model ;
64- boolean merge =false ;
65- boolean rolesSet =false ;
66- Role fromRole =null ;
6766 boolean optional =false ;
6867
6968 projected .derivedFrom (source );
@@ -80,9 +79,7 @@ public Object project(ProjectorContext context, ModelObject model,
8079 l );
8180
8281 if (block != null ) {
83-
84- // TODO: Temporary fix to merge nested choice
85- if (block .getContents ().size () == 1
82+ if (block .getContents ().size () == 1
8683 && block .getContents ().get (0 ) instanceof Choice
8784 && isSameRole (projected , (Choice )block .getContents ().get (0 ))) {
8885 projected .getPaths ().addAll (((Choice )block .getContents ().get (0 )).getPaths ());
@@ -93,82 +90,34 @@ && isSameRole(projected, (Choice)block.getContents().get(0))) {
9390 optional = true ;
9491 }
9592 }
96-
97- // Check if block needs to be merged
98- if (merge ) {
99- Role destination =null ;
100-
101- // Check if initial interactions have same destination
102- for (Block block : projected .getPaths ()) {
103-
104- java .util .List <ModelObject > list =
105- org .scribble .protocol .util .InteractionUtil .getInitialInteractions (block );
106- for (ModelObject act : list ) {
107- Role r =InteractionUtil .getToRole (act );
108-
109- if (destination == null ) {
110- destination = r ;
111- } else if (!destination .equals (r )) {
112- merge = false ;
113- break ;
114- }
115- }
116-
117- if (!merge ) {
118- break ;
119- }
120- }
121- }
12293
123- if (merge ) {
124- java .util .List <Block > tmp =new java .util .Vector <Block >(projected .getPaths ());
125-
126- for (Block block : tmp ) {
127- java .util .List <ModelObject > list =
128- org .scribble .protocol .util .InteractionUtil .getInitialInteractions (block );
129-
130- // Remove block
131- projected .getPaths ().remove (block );
132-
133- for (ModelObject act : list ) {
134- MessageSignature ms =InteractionUtil .getMessageSignature (act );
135- boolean add =true ;
136-
137- for (Block wb : projected .getPaths ()) {
138- MessageSignature wbms =InteractionUtil .getMessageSignature (wb );
139-
140- if (ms .equals (wbms )) {
141- // TODO: Need to check paths for conformance.
142- // If conforms, then merge, if not, then error
143- l .error ("MERGING NOT CURRENTLY SUPPORTED" , null );
144-
145- add = false ;
146- }
147- }
148-
149- if (add ) {
150-
151- // Check if roles should be set or matched
152- if (!rolesSet ) {
153- fromRole = InteractionUtil .getFromRole (act );
154- //toRole = InteractionUtil.getToRole(act);
155-
156- rolesSet = true ;
157- } else {
158- // TODO: Check that roles match
159- LOG .info ("TODO: Check that roles match" );
160- }
161- }
162- }
163- }
164- }
165-
166- if (merge ) {
167- projected .setRole (fromRole );
168- }
94+ return (processChoice (context , projected , role , l , optional ));
95+ }
96+
97+ /**
98+ * This method processes a choice construct.
99+ *
100+ * @param context The context
101+ * @param projected The projected choice
102+ * @param role The projected role
103+ * @param l The journal
104+ * @param optional Whether the construct is optional
105+ * @return The processed activities
106+ */
107+ protected static Object processChoice (ProjectorContext context , Choice projected , Role role ,
108+ Journal l , boolean optional ) {
109+ Object ret =null ;
169110
170111 ret = extractCommonBehaviour (context , projected , role , l );
171112
113+
114+ // If multiple paths have common initiator (but not common to all behaviours)
115+ // then group in sub-choice
116+ groupSubpathsWithCommonInitiator (context , projected , role , l );
117+
118+ // Confirm each path has distinct initial interactions
119+ checkForAmbiguity (context , projected , role , l );
120+
172121 // Remove all empty paths
173122 for (int i =projected .getPaths ().size ()-1 ; i >= 0 ; i --) {
174123 Block b =projected .getPaths ().get (i );
@@ -187,12 +136,102 @@ && isSameRole(projected, (Choice)block.getContents().get(0))) {
187136 }
188137 projected = null ;
189138 } else if (optional ) {
139+
140+ // Check if optional permitted
141+ if (System .getProperty (ALLOW_OPTIONAL , "false" ).
142+ equalsIgnoreCase (Boolean .FALSE .toString ())){
143+ l .error (MessageFormat .format (
144+ java .util .PropertyResourceBundle .getBundle ("org.scribble.protocol.projection.Messages" ).
145+ getString ("_CHOICE_EMPTY_PATH" ),
146+ role .getName ()), projected .getProperties ());
147+ }
148+
190149 // Add optional block
191150 projected .getPaths ().add (new Block ());
192151 }
193152
194153 return (ret );
195154 }
155+
156+ /**
157+ * This method checks whether choice paths should be grouped into sub-paths
158+ * with common initiating interaction sentences.
159+ *
160+ * @param context The context
161+ * @param projected The projected choice
162+ * @param role The role
163+ * @param l The journal
164+ */
165+ @ SuppressWarnings ("unchecked" )
166+ protected static void groupSubpathsWithCommonInitiator (ProjectorContext context , Choice projected ,
167+ Role role , Journal l ) {
168+ java .util .Map <Activity , java .util .List <Block >> pathGroups =
169+ new java .util .HashMap <Activity , java .util .List <Block >>();
170+
171+ for (Block path : projected .getPaths ()) {
172+ if (path .size () > 0 ) {
173+ java .util .List <Block > plist =pathGroups .get (path .get (0 ));
174+ if (plist == null ) {
175+ plist = new java .util .Vector <Block >();
176+ pathGroups .put (path .get (0 ), plist );
177+ }
178+ plist .add (path );
179+ }
180+ }
181+
182+ for (Activity act : pathGroups .keySet ()) {
183+ java .util .List <Block > plist =pathGroups .get (act );
184+
185+ if (plist .size () >= 2 ) {
186+
187+ // Create new choice construct
188+ Choice sub =new Choice ();
189+ int pos =-1 ;
190+
191+ // TODO: Do we need to determine/set the role associated with choice?
192+ // If different then report error?
193+
194+ for (Block b : plist ) {
195+ if (pos == -1 ) {
196+ pos = projected .getPaths ().indexOf (b );
197+ }
198+ projected .getPaths ().remove (b );
199+ sub .getPaths ().add (b );
200+ }
201+
202+ Block newPath =new Block ();
203+
204+ projected .getPaths ().add (pos , newPath );
205+
206+ Object processed =processChoice (context , sub , role , l , false );
207+
208+ if (processed instanceof java .util .List <?>) {
209+ newPath .getContents ().addAll ((java .util .List <Activity >)processed );
210+ } else {
211+ LOG .severe ("Should have returned a list with extracted common activity(s) followed by choice" );
212+ }
213+ }
214+ }
215+ }
216+
217+ protected static void checkForAmbiguity (ProjectorContext context , Choice projected ,
218+ Role role , Journal l ) {
219+ // Confirm each path has distinct initial interactions
220+ java .util .List <ModelObject > interactions =new java .util .Vector <ModelObject >();
221+
222+ for (Block path : projected .getPaths ()) {
223+ java .util .List <ModelObject > initial =InteractionUtil .getInitialInteractions (path );
224+
225+ if (!Collections .disjoint (interactions , initial )) {
226+ l .error (MessageFormat .format (
227+ java .util .PropertyResourceBundle .getBundle ("org.scribble.protocol.projection.Messages" ).
228+ getString ("_AMBIGUOUS_CHOICE" ),
229+ role .getName ()), null );
230+ } else {
231+ interactions .addAll (initial );
232+ }
233+ }
234+ }
196235
197236 /**
198237 * Extract common behaviour.
@@ -240,33 +279,6 @@ protected static Object extractCommonBehaviour(ProjectorContext context, Choice
240279 projected .getPaths ().get (i ).getContents ().remove (0 );
241280 }
242281 } else {
243- // Check if two or more paths have same first element, and
244- // therefore are invalid
245- boolean invalid =false ;
246-
247- for (int i =0 ; !invalid && i < projected .getPaths ().size (); i ++) {
248-
249- for (int j =0 ; !invalid && j < projected .getPaths ().size (); j ++) {
250-
251- if (i != j ) {
252- Block b1 =projected .getPaths ().get (i );
253- Block b2 =projected .getPaths ().get (j );
254-
255- if (b1 .size () > 0 && b2 .size () > 0
256- && b1 .get (0 ).equals (b2 .get (0 ))) {
257- invalid = true ;
258- }
259- }
260- }
261- }
262-
263- if (invalid ) {
264- l .error (MessageFormat .format (
265- java .util .PropertyResourceBundle .getBundle ("org.scribble.protocol.projection.Messages" ).
266- getString ("_AMBIGUOUS_CHOICE" ),
267- role .getName ()), null );
268- }
269-
270282 checkPaths = false ;
271283 }
272284
0 commit comments