/* * JBoss, Home of Professional Open Source * Copyright 2012, Red Hat Middleware LLC, and others contributors as indicated * by the @authors tag. All rights reserved. * See the copyright.txt in the distribution for a * full listing of individual contributors. * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package org.savara.protocol.internal.aggregator; import java.util.logging.Level; import java.util.logging.Logger; import org.savara.common.logging.DefaultFeedbackHandler; import org.savara.common.logging.FeedbackHandler; import org.savara.common.model.annotation.AnnotationDefinitions; import org.savara.protocol.aggregator.ProtocolAggregator; import org.savara.protocol.internal.aggregator.LocalProtocolUnit.ActivityCursor; import org.savara.protocol.model.util.RepeatUtil; import org.scribble.common.model.Annotation; import org.scribble.protocol.model.Activity; import org.scribble.protocol.model.Block; import org.scribble.protocol.model.Choice; import org.scribble.protocol.model.DefaultVisitor; import org.scribble.protocol.model.ImportList; import org.scribble.protocol.model.Interaction; import org.scribble.protocol.model.Introduces; import org.scribble.protocol.model.ParameterDefinition; import org.scribble.protocol.model.Protocol; import org.scribble.protocol.model.ProtocolImportList; import org.scribble.protocol.model.ProtocolModel; import org.scribble.protocol.model.Repeat; import org.scribble.protocol.model.Role; import org.scribble.protocol.model.TypeImport; import org.scribble.protocol.model.TypeImportList; import org.scribble.protocol.util.RoleUtil; /** * This is the default implementation of the protocol aggregator * implementation. * */ public class ProtocolAggregatorImpl implements ProtocolAggregator { private static final Logger LOG=Logger.getLogger(ProtocolAggregatorImpl.class.getName()); /** * {@inheritDoc} */ public ProtocolModel aggregateGlobalModel(String protocolName, String namespace, java.util.Collection<ProtocolModel> locals, FeedbackHandler handler) { ProtocolModel ret=new ProtocolModel(); Protocol protocol=new Protocol(); protocol.setName(protocolName); ret.setProtocol(protocol); // Define protocol namespace org.savara.common.model.annotation.Annotation protocolAnn= new org.savara.common.model.annotation.Annotation(AnnotationDefinitions.PROTOCOL); protocolAnn.getProperties().put(AnnotationDefinitions.NAMESPACE_PROPERTY, (namespace == null ? "http://namespace" : namespace)); protocol.getAnnotations().add(protocolAnn); // Merge imports mergeImports(ret, locals, handler); // Check all local protocols have same name, and different roles java.util.Map<Role, LocalProtocolUnit> protocolUnits= new java.util.HashMap<Role, LocalProtocolUnit>(); GlobalProtocolUnit gpu=new GlobalProtocolUnit(ret); java.util.List<Role> clientRoles=new java.util.Vector<Role>(); java.util.List<Role> serverRoles=new java.util.Vector<Role>(); for (ProtocolModel local : locals) { // Merge annotations mergeAnnotations(ret.getProtocol().getAnnotations(), local.getProtocol().getAnnotations(), handler); if (protocol.getName() == null) { protocol.setName(local.getProtocol().getName()); } if (local.getProtocol().getLocatedRole() == null) { throw new IllegalArgumentException("Located role not defined"); } if (protocolUnits.containsKey(local.getProtocol().getLocatedRole())) { throw new IllegalArgumentException("Local model for role '"+ local.getProtocol().getLocatedRole()+"' already exists"); } LocalProtocolUnit lpu=new LocalProtocolUnit(local); protocolUnits.put(local.getProtocol().getLocatedRole(), lpu); // Check for client roles for (ParameterDefinition pd : local.getProtocol().getParameterDefinitions()) { if (pd.getRole() != null) { clientRoles.add(pd.getRole()); } } serverRoles.add(local.getProtocol().getLocatedRole()); } clientRoles.removeAll(serverRoles); Role clientRole=null; if (clientRoles.size() > 0) { clientRole = clientRoles.get(0); if (clientRoles.size() > 1) { LOG.warning("More than one client roles found for aggregation"); } } while (processProtocolUnits(gpu, protocolUnits, clientRole, handler)); // Post process global model postProcessGlobal(ret); // TODO: Should check for incomplete local protocols? return(ret); } /** * This method post-processes a global model to complete the * description. * * @param global The global model */ protected void postProcessGlobal(ProtocolModel global) { global.visit(new DefaultVisitor() { public void end(Repeat elem) { if (elem.getRoles().size() == 0) { // Find decision maker Role decisionMaker=RepeatUtil.getDecisionMaker(elem); if (decisionMaker != null) { elem.getRoles().add(decisionMaker); } else if (LOG.isLoggable(Level.FINE)) { LOG.fine("Could not find decision maker for repeat: "+elem); } } } }); } protected Activity createActivity(Activity act) { Activity ret=null; if (act instanceof Introduces) { ret = new Introduces((Introduces)act); } return(ret); } protected boolean processProtocolUnits(GlobalProtocolUnit gpu, java.util.Map<Role, LocalProtocolUnit> lpus, Role client, FeedbackHandler handler) { boolean ret=false; LOG.finest("Process protocol units: "+lpus+" clientRole="+client); // Find sending protocol unit for (LocalProtocolUnit lpu : lpus.values()) { ActivityCursor cursor = lpu.getSendingCursor(); LOG.finest("LocalPU ("+lpu+") Sending cursor: "+cursor); if (cursor != null) { Activity send=cursor.peek(); LocalProtocolUnit receiver=getReceiver(send, lpus); ActivityCursor receiveCursor=null; if (receiver != null) { receiveCursor = receiver.findReceivingCursor(send); } else { LOG.warning("Couldn't find receive for sending action '"+send+"'"); } LOG.finest("LocalPU ("+lpu+") Send="+send+" ReceiverPU=" +receiver+" ReceiveCursor="+receiveCursor); if (receiveCursor != null) { Activity receive=receiveCursor.getReceiveAction(send); LOG.finest("LocalPU ("+lpu+") Receive="+receive); if (receive != null) { gpu.process(send, cursor, receive, receiveCursor); ret = true; LOG.finest("LocalPU ("+lpu +") Move send/receive cursors to next activity"); receiveCursor.next(); cursor.next(); } } } if (ret) { LOG.finest("LocalPU ("+lpu+") Ret="+ret+" so break."); break; } } // Check for repetition for (Role r : lpus.keySet()) { java.util.List<Role> roles=new java.util.LinkedList<Role>(); java.util.List<Role> processed=new java.util.LinkedList<Role>(); java.util.Map<Role, ActivityCursor> cursors=new java.util.HashMap<Role, ActivityCursor>(); roles.add(r); LOG.finest("Check for repetition: role="+r); for (int i=0; i < roles.size(); i++) { Role rr=roles.get(i); LocalProtocolUnit lpu=lpus.get(rr); ActivityCursor cursor=lpu.getRepetitionCursor(); LOG.finest("Other role="+rr+" lpu="+lpu+" cursor="+cursor); if (cursor != null) { cursors.put(rr, cursor); for (Role deprole : cursor.getRepetitionRoles()) { if (!roles.contains(deprole)) { roles.add(deprole); } } processed.add(rr); } else { break; } } // Repetition is detected if roles contains more than the // original role. All relevant roles will be synchronized // if the number of roles is equal to the number processed. // Otherwise ignore for now. if (roles.size() > 1 && roles.size() == processed.size()) { LOG.finest("Generate repetition: "+cursors.values()); // Generate global repetition and process gpu.processRepetition(cursors.values()); for (ActivityCursor cursor : cursors.values()) { cursor.next(); } ret = true; } } // If no send/receive match, then check for individual actions that may // help make progress through the local projections if (!ret) { LOG.finest("Check for endpoint specific actions"); // Check for endpoint specific actions that can be transferred for (LocalProtocolUnit lpu : lpus.values()) { ActivityCursor individualCursor=null; do { individualCursor=lpu.getIndividualCursor(); LOG.finest("LPU Individual cursor="+individualCursor); if (individualCursor != null) { Activity individual=individualCursor.peek(); LOG.finest("Process individual activity="+individual); gpu.process(individual, individualCursor); individualCursor.next(); ret = true; } } while (individualCursor != null); } } // Last chance - if client role specified, then just look // for a receiver activity with the client role as sender if (!ret && client != null) { for (LocalProtocolUnit lpu : lpus.values()) { ActivityCursor cursor = lpu.getActivityWithClientCursor(client); LOG.finest("LocalPU ("+lpu+") Activity with client ("+client +") cursor: "+cursor); if (cursor != null) { Activity individual=cursor.peek(); LOG.finest("Process activity with client activity="+individual); gpu.process(individual, cursor); cursor.next(); ret = true; break; } } } LOG.finest("Process protocol units: ret="+ret); return(ret); } protected LocalProtocolUnit getReceiver(Activity act, java.util.Map<Role, LocalProtocolUnit> lpus) { if (act instanceof Interaction) { if (((Interaction)act).getToRoles().size() > 0) { return(lpus.get(((Interaction)act).getToRoles().get(0))); } /* } else if (act instanceof Choice) { // Need to find a role, that is not the choice decision maker, // that has the choice associated with the decision maker Role decisionMaker=((Choice)act).getRole(); for (Role role : lpus.keySet()) { if (!role.equals(decisionMaker)) { LocalProtocolUnit lpu=lpus.get(role); ActivityCursor cursor=lpu.findReceivingCursor(act); if (cursor != null) { return(lpu); } } } */ } return(null); } /** * {@inheritDoc} */ public ProtocolModel aggregateLocalModel(String protocolName, java.util.Collection<ProtocolModel> locals, FeedbackHandler handler) { ProtocolModel ret=new ProtocolModel(); Protocol protocol=new Protocol(); protocol.setName(protocolName); ret.setProtocol(protocol); // Merge imports mergeImports(ret, locals, handler); // Verify all local models are associated with the same role Role role=null; String introducingRole=null; for (ProtocolModel lm : locals) { // Merge annotations mergeAnnotations(ret.getProtocol().getAnnotations(), lm.getProtocol().getAnnotations(), handler); if (role == null) { role = lm.getProtocol().getLocatedRole(); } else if (!role.equals(lm.getProtocol().getLocatedRole())) { throw new IllegalArgumentException("Local models must all be associated with the same located role"); } if (introducingRole == null) { if (lm.getProtocol().getParameterDefinitions().size() > 0) { introducingRole = lm.getProtocol().getParameterDefinitions().get(0).getName(); } } else if (lm.getProtocol().getParameterDefinitions().size() > 0) { if (!introducingRole.equals(lm.getProtocol().getParameterDefinitions().get(0).getName())) { throw new IllegalArgumentException("Local models must all be associated with the same role parameter"); } } } protocol.setLocatedRole(new Role(role)); if (introducingRole != null) { ParameterDefinition pd=new ParameterDefinition(); pd.setName(introducingRole); protocol.getParameterDefinitions().add(pd); } Block b=new Block(); protocol.setBlock(b); // Build list of external roles being interacted with java.util.List<Role> introducedRoles=new java.util.Vector<Role>(); java.util.List<Block> sourcePaths=new java.util.Vector<Block>(); for (ProtocolModel lm : locals) { Block lb=lm.getProtocol().getBlock(); if (lb.size() > 0 && lb.get(0) instanceof Introduces) { Introduces i=(Introduces)lb.get(0); for (Role r : i.getIntroducedRoles()) { if (!introducedRoles.contains(r)) { introducedRoles.add(new Role(r)); } } // Remove introduces lb.getContents().remove(0); } sourcePaths.add(lb); } // Merge paths from individual local models into single local model mergePaths(sourcePaths, b, handler); postProcessMerged(b, handler); // Establish 'introduces' clauses in the correct scopes for (Role ir : introducedRoles) { // Find enclosing block associated with the roles Block block=RoleUtil.getEnclosingBlock(protocol, ir, false); Introduces intro=null; if (block.size() > 0 && block.get(0) instanceof Introduces) { intro = (Introduces)block.get(0); } else { intro = new Introduces(); intro.setIntroducer(new Role(role)); block.getContents().add(0, intro); } intro.getIntroducedRoles().add(ir); } return(ret); } protected void mergeImports(ProtocolModel aggregated, java.util.Collection<ProtocolModel> locals, FeedbackHandler handler) { java.util.List<String> typeNames=new java.util.Vector<String>(); for (ProtocolModel local : locals) { for (ImportList implist : local.getImports()) { if (implist instanceof TypeImportList) { TypeImportList til=(TypeImportList)implist; for (TypeImport ti : til.getTypeImports()) { if (!typeNames.contains(ti.getName())) { typeNames.add(ti.getName()); TypeImport copy=new TypeImport(ti); TypeImportList til2=new TypeImportList(); til2.setFormat(til.getFormat()); til2.setLocation(til.getLocation()); til2.getTypeImports().add(copy); aggregated.getImports().add(til2); } } } else if (implist instanceof ProtocolImportList) { // TODO: } } } } protected void mergeAnnotations(java.util.List<Annotation> main, java.util.List<Annotation> source, FeedbackHandler handler) { int ansCount=0; for (Annotation ann : source) { if (ann instanceof org.savara.common.model.annotation.Annotation) { if (!main.contains(ann)) { // If annotation is a Type, then need to check if namespace // has already been declare if (!((org.savara.common.model.annotation.Annotation) ann).getName().equals( AnnotationDefinitions.TYPE) || AnnotationDefinitions.getAnnotationWithProperty( main, AnnotationDefinitions.TYPE, AnnotationDefinitions.NAMESPACE_PROPERTY, ((org.savara.common.model.annotation.Annotation) ann).getProperties(). get( AnnotationDefinitions.NAMESPACE_PROPERTY)) == null) { org.savara.common.model.annotation.Annotation newAnn= new org.savara.common.model.annotation.Annotation( (org.savara.common.model.annotation.Annotation)ann); // Check if prefix is duplicated if (AnnotationDefinitions.getAnnotationWithProperty(main, AnnotationDefinitions.TYPE, AnnotationDefinitions.PREFIX_PROPERTY, ((org.savara.common.model.annotation.Annotation) ann).getProperties(). get( AnnotationDefinitions.PREFIX_PROPERTY)) != null) { newAnn.getProperties().put(AnnotationDefinitions.PREFIX_PROPERTY, "ans"+(ansCount++)); } main.add(newAnn); } } } } } protected void mergePaths(java.util.List<Block> sourcePaths, Block targetPath, FeedbackHandler handler) { if (sourcePaths.size() == 0) { return; } else if (sourcePaths.size() == 1) { targetPath.getContents().addAll(sourcePaths.get(0).getContents()); return; } while (transferCommonComponent(targetPath, targetPath.size(), sourcePaths, 0, handler)); if (sourcePaths.size() > 0) { Choice choice=new Choice(); targetPath.add(choice); int pos=targetPath.indexOf(choice); while (transferCommonComponent(targetPath, pos+1, sourcePaths, -1, handler)); // Group into paths with common first interaction boolean optional=false; boolean content=false; while (sourcePaths.size() > 0) { Block path=sourcePaths.get(0); if (path.size() == 0) { optional = true; } else { content = true; Object component=path.get(0); java.util.List<Block> sps=new java.util.Vector<Block>(); sps.add(path); // Check if other paths have the same initial component for (int i=1; i < sourcePaths.size(); i++) { Block path2=sourcePaths.get(i); if (path2.size() > 0 && path2.get(0).equals(component)) { sps.add(path2); sourcePaths.remove(i); i--; // Decrement due to removed element } } if (sps.size() == 1) { choice.getPaths().add(sps.get(0)); } else { // Merge paths Block tp=new Block(); choice.getPaths().add(tp); mergePaths(sps, tp, handler); } } sourcePaths.remove(0); } // If no content, then remove the choice construct if (!content) { targetPath.remove(choice); } else { if (optional) { // Add empty path choice.getPaths().add(new Block()); } // Check for located role Role role=null; for (Block b : choice.getPaths()) { if (b.size() > 0 && b.get(0) instanceof Interaction) { Interaction in=(Interaction)b.get(0); if (in.getFromRole() == null) { role = in.getEnclosingProtocol().getLocatedRole(); break; } else { role = in.getFromRole(); break; } } } choice.setRole(new Role(role)); } } } protected void postProcessMerged(Block targetPath, FeedbackHandler handler) { targetPath.visit(new DefaultVisitor() { public boolean start(Choice choice) { checkForRepetition(choice); return(true); } }); } /** * This method recursively determines whether the choice is an * appropriate structure to represent a repetition. A valid * structure has at most one child path containing a choice, * and the choice must be preceded by activities which represent * the repeated activities. The choice may be optionally * followed by activities that would follow the repetition. * * @param choice The choice * @return Whether the structure is valid for a repetition */ protected boolean isChoiceValidForRepetition(Choice choice) { boolean ret=true; Choice child=null; for (Block path : choice.getPaths()) { for (int i=0; i < path.getContents().size(); i++) { if (path.getContents().get(i) instanceof Choice) { if (child != null) { ret = false; } else { child = (Choice)path.getContents().get(i); if (i == 0) { ret = false; // Other activities should preceed the choice } else if (!choice.getRole().equals(child.getRole())) { ret = false; // Needs to be same decision maker } } } if (!ret) { break; } } if (!ret) { break; } } if (ret && child == null) { ret = false; } return(ret); } protected void checkForRepetition(Choice choice) { // Check if top level choice and has a valid structure to // represent a repetition if ((choice.getParent() == null || choice.getParent().getParent() == null || !(choice.getParent().getParent() instanceof Choice)) && isChoiceValidForRepetition(choice)) { final java.util.List<Block> repeated=new java.util.Vector<Block>(); final java.util.List<Block> nonrepeated=new java.util.Vector<Block>(); choice.visit(new DefaultVisitor() { public boolean start(Block block) { boolean ret=false; // Check if contains a choice for (Activity act : block.getContents()) { if (act instanceof Choice) { ret = true; break; } } if (ret) { // Separate the activities before and after the choice // into repeating and non-repeating blocks Block b=new Block(); for (Activity act : block.getContents()) { if (act instanceof Choice) { repeated.add(b); b = new Block(); } else { b.add(act); } } if (b.size() > 0) { nonrepeated.add(b); } } else { // Check if choice is a terminating repetition // condition, where one path is the repeating activities // and the other is empty Choice parent=(Choice)block.getParent(); if (parent.getPaths().size() == 2 && (parent.getPaths().get(0).size() == 0 || parent.getPaths().get(1).size() == 0)) { // Repeating block - but only the one with contents // is relevant if (block.size() > 0) { repeated.add(block); } } else { // Non repeating block nonrepeated.add(block); } } return(ret); } }); //Role locatedRole=choice.getEnclosingProtocol().getLocatedRole(); Block parent=(Block)choice.getParent(); int pos=parent.indexOf(choice); Repeat repeat=new Repeat(); Block merged=new Block(); repeat.setBlock(merged); parent.getContents().add(pos+1, repeat); mergePaths(repeated, merged, new DefaultFeedbackHandler()); parent.getContents().remove(pos); // Get decision role Role decisionMaker=RepeatUtil.getDecisionMaker(repeat); if (decisionMaker != null) { repeat.getRoles().add(decisionMaker); } else { decisionMaker = repeat.getEnclosingProtocol().getLocatedRole(); if (decisionMaker != null) { repeat.getRoles().add(decisionMaker); } } Repeat repeat2=new Repeat(); Block merged2=new Block(); repeat2.setBlock(merged2); // Need to create a temporary protocol to provide the located role information // to the merging algorithm /* Protocol p=new Protocol(); p.setLocatedRole(locatedRole); Choice c2=new Choice(); c2.getPaths().addAll(nonrepeated); p.getBlock().add(c2); */ parent.getContents().add(pos+1, repeat2); mergePaths(nonrepeated, merged2, new DefaultFeedbackHandler()); parent.getContents().remove(pos+1); parent.getContents().addAll(pos+1, merged2.getContents()); } } protected boolean transferCommonComponent(Block targetPath, int targetPos, java.util.List<Block> sourcePaths, int sourcePos, FeedbackHandler handler) { boolean ret=false; if (sourcePaths.size() > 0 && sourcePaths.get(0).size() > 0) { Interaction component=null; if (sourcePos == -1) { // Get last element component = (Interaction)sourcePaths.get(0).get(sourcePaths.get(0).size()-1); } else { // Get component at source position component = (Interaction)sourcePaths.get(0).get(sourcePos); } for (int i=1; i < sourcePaths.size(); i++) { Interaction ref=null; if (sourcePaths.get(i).size() > 0) { if (sourcePos == -1) { // Get last element ref = (Interaction)sourcePaths.get(i).get(sourcePaths.get(i).size()-1); } else { // Get ref component at source position ref = (Interaction)sourcePaths.get(i).get(sourcePos); } ret = component.equals(ref); if (!ret) { break; } } else { ret = false; break; } } if (ret) { Interaction in=new Interaction(component); targetPath.getContents().add(targetPos, in); // Remove common components for (int i=sourcePaths.size()-1; i >= 0; i--) { if (sourcePos == -1) { sourcePaths.get(i).getContents().remove(sourcePaths.get(i).size()-1); } else { sourcePaths.get(i).getContents().remove(sourcePos); } } } } return(ret); } }