/*
* Copyright 2009-10 www.scribble.org
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.savara.protocol.model.stateless;
import java.text.MessageFormat;
import java.util.logging.Logger;
import org.scribble.common.logging.Journal;
import org.scribble.protocol.model.Activity;
import org.scribble.protocol.model.Block;
import org.scribble.protocol.model.Choice;
import org.scribble.protocol.model.Role;
/**
* This class provides the Choice Merging utility function.
*
* NOTE: This is a copy of the Scribble class that will be available
* in 2.1.0.M2 onwards, and should switch back to the scribble class then.
*/
public class ChoiceMergingUtil {
/**
* System property: should choice constructs be permitted to have empty paths
* and therefore be optional?
*/
public static final String ALLOW_OPTIONAL = "scribble.choice.allowOptional";
private static final Logger LOG=Logger.getLogger(ChoiceMergingUtil.class.getName());
/**
* This method processes a choice construct.
*
* @param context The context
* @param projected The projected choice
* @param role The projected role
* @param l The journal
* @param optional Whether the construct is optional
* @return The processed activities
*/
public static Object merge(Choice projected, Role role,
Journal l, boolean optional) {
Object ret=null;
ret = extractCommonBehaviour(projected, role, l);
// If multiple paths have common initiator (but not common to all behaviours)
// then group in sub-choice
groupSubpathsWithCommonInitiator(projected, role, l);
// Remove all empty paths
for (int i=projected.getPaths().size()-1; i >= 0; i--) {
Block b=projected.getPaths().get(i);
if (b.size() == 0) {
projected.getPaths().remove(i);
optional = true;
}
}
if (projected.getPaths().size() == 0) {
if (ret == projected) {
ret = null;
} else {
((java.util.List<?>)ret).remove(projected);
}
projected = null;
} else if (optional) {
// Check if optional permitted
if (System.getProperty(ALLOW_OPTIONAL, "false").
equalsIgnoreCase(Boolean.FALSE.toString())) {
l.error(MessageFormat.format(
java.util.PropertyResourceBundle.getBundle(
"org.scribble.protocol.projection.impl.Messages").
getString("_CHOICE_EMPTY_PATH"),
role.getName()), projected.getProperties());
}
// Add optional block
projected.getPaths().add(new Block());
}
return (ret);
}
/**
* This method checks whether choice paths should be grouped into sub-paths
* with common initiating interaction sentences.
*
* @param projected The projected choice
* @param role The role
* @param l The journal
*/
@SuppressWarnings("unchecked")
protected static void groupSubpathsWithCommonInitiator(Choice projected,
Role role, Journal l) {
java.util.Map<Activity, java.util.List<Block>> pathGroups=
new java.util.HashMap<Activity, java.util.List<Block>>();
for (Block path : projected.getPaths()) {
if (path.size() > 0) {
java.util.List<Block> plist=pathGroups.get(path.get(0));
if (plist == null) {
plist = new java.util.Vector<Block>();
pathGroups.put(path.get(0), plist);
}
plist.add(path);
}
}
for (Activity act : pathGroups.keySet()) {
java.util.List<Block> plist=pathGroups.get(act);
if (plist.size() >= 2) {
// Create new choice construct
Choice sub=new Choice();
int pos=-1;
// TODO: Do we need to determine/set the role associated with choice?
// If different then report error?
for (Block b : plist) {
if (pos == -1) {
pos = projected.getPaths().indexOf(b);
}
projected.getPaths().remove(b);
sub.getPaths().add(b);
}
Block newPath=new Block();
projected.getPaths().add(pos, newPath);
Object processed=merge(sub, role, l, false);
if (processed instanceof java.util.List<?>) {
newPath.getContents().addAll((java.util.List<Activity>)processed);
} else {
LOG.severe("Should have returned a list with extracted common activity(s) followed by choice");
}
}
}
}
/**
* Extract common behaviour.
*
* @param projected The projected choice
* @param role The role
* @param l The journal
* @return Extracted common behaviour
*/
@SuppressWarnings("unchecked")
protected static Object extractCommonBehaviour(Choice projected,
Role role, Journal l) {
Object ret=projected;
// Check to see whether common interaction sentences can be extracted
// out from each path to precede the choice
boolean checkPaths=true;
do {
boolean allSame=projected.getPaths().size() > 1;
for (int i=1; allSame && i < projected.getPaths().size(); i++) {
Block b1=projected.getPaths().get(0);
Block b2=projected.getPaths().get(i);
if (b1.size() == 0 || b2.size() == 0) {
allSame = false;
} else if (!b1.get(0).equals(b2.get(0))) {
allSame = false;
}
}
if (allSame) {
// Merge first elements from each path and place before the choice
if (!(ret instanceof java.util.List<?>)) {
ret = new java.util.Vector<Object>();
((java.util.List<Object>)ret).add(projected);
}
((java.util.List<Object>)ret).add(((java.util.List<Object>)ret).size()-1,
projected.getPaths().get(0).getContents().get(0));
for (int i=0; i < projected.getPaths().size(); i++) {
// Remove first element
projected.getPaths().get(i).getContents().remove(0);
}
} else {
checkPaths = false;
}
} while(checkPaths);
return (ret);
}
/**
* Check whether roles are the same.
*
* @param c1 First choice
* @param c2 Second choice
* @return Whether roles are same
*/
protected boolean isSameRole(Choice c1, Choice c2) {
if (c1.getRole() == null && c2.getRole() == null) {
return (true);
} else {
return (c1.getRole().equals(c2.getRole()));
}
}
}