001package org.nasdanika.html.flow; 002 003import java.util.Collection; 004import java.util.HashMap; 005import java.util.HashSet; 006import java.util.Map; 007import java.util.Map.Entry; 008import java.util.Set; 009import java.util.function.BiConsumer; 010import java.util.function.Consumer; 011import java.util.function.Predicate; 012import java.util.stream.Collectors; 013 014import org.eclipse.emf.common.util.EList; 015import org.eclipse.emf.ecore.EObject; 016import org.nasdanika.common.Context; 017import org.nasdanika.common.ProgressMonitor; 018import org.nasdanika.common.Util; 019import org.nasdanika.diagram.Diagram; 020import org.nasdanika.flow.Call; 021import org.nasdanika.flow.Flow; 022import org.nasdanika.flow.FlowElement; 023import org.nasdanika.flow.FlowPackage; 024import org.nasdanika.flow.PseudoState; 025import org.nasdanika.flow.Transition; 026import org.nasdanika.flow.util.FlowStateDiagramGenerator; 027import org.nasdanika.html.model.app.Action; 028import org.nasdanika.html.model.app.AppFactory; 029import org.nasdanika.html.model.app.Label; 030import org.nasdanika.html.model.app.NavigationPanel; 031import org.nasdanika.html.model.app.NavigationPanelStyle; 032import org.nasdanika.ncore.Marker; 033import org.nasdanika.ncore.util.NamedElementComparator; 034 035public class FlowActionBuilder extends ActivityActionBuilder<Flow> { 036 037 public FlowActionBuilder(Flow value, Context context) { 038 super(value, context); 039 } 040 041 @Override 042 protected Action buildAction( 043 Action action, 044 BiConsumer<EObject, Action> registry, 045 Consumer<org.nasdanika.common.Consumer<org.nasdanika.html.emf.EObjectActionResolver.Context>> resolveConsumer, 046 ProgressMonitor progressMonitor) throws Exception { 047 048 action = super.buildAction(action, registry, resolveConsumer, progressMonitor); 049 EList<EObject> children = action.getChildren(); 050 Predicate<FlowElement<?>> isPseudoState= PseudoState.class::isInstance; 051 for (FlowElement<?> element: getTarget().getElements().values().stream().filter(isPseudoState.negate()).sorted(FlowActionBuilder::compareFlowElements).collect(Collectors.toList())) { 052 children.add(createChildAction(element, registry, resolveConsumer, progressMonitor)); 053 } 054 EList<Action> anonymous = action.getAnonymous(); 055 for (FlowElement<?> element: getTarget().getElements().values().stream().filter(isPseudoState).collect(Collectors.toList())) { 056 anonymous.add(createChildAction(element, registry, resolveConsumer, progressMonitor)); 057 } 058 059 return action; 060 } 061 062 public static int compareFlowElements(FlowElement<?> a, FlowElement<?> b) { 063 if (a == b) { 064 return 0; 065 } 066 067 String asg = a.getSortGroup(); 068 String bsg = b.getSortGroup(); 069 if (Util.isBlank(asg)) { 070 if (!Util.isBlank(bsg)) { 071 return -1; 072 } 073 } else { 074 if (Util.isBlank(bsg)) { 075 return 1; 076 } 077 int cmp = asg.compareTo(bsg); 078 if (cmp != 0) { 079 return cmp; 080 } 081 } 082 083 int abDistance = distance(a, b, new HashSet<>()); 084 int baDistance = distance(b, a, new HashSet<>()); 085 086 if (abDistance != -1 && baDistance == -1) { 087 return -1; 088 } 089 090 if (baDistance != -1 && abDistance == -1) { 091 return 1; 092 } 093 094 // Cycle 095 if (abDistance != -1 && baDistance != -1) { 096 return abDistance - baDistance; 097 } 098 099 // Common source - shortest sum. 100 Map<FlowElement<?>, Integer> aSources = sources(a, new HashSet<>()); 101 int da = 0; 102 int db = 0; 103 FlowElement<?> cs = null; 104 for (Entry<FlowElement<?>, Integer> bse: sources(b, new HashSet<>()).entrySet()) { 105 for (Entry<FlowElement<?>, Integer> ase: aSources.entrySet()) { 106 if (ase.getKey() == bse.getKey()) { 107 int sd = ase.getValue() + bse.getValue(); 108 if (cs == null || sd < da + db) { 109 da = ase.getValue(); 110 db = bse.getValue(); 111 cs = ase.getKey(); 112 } 113 } 114 } 115 } 116 if (cs != null && da != db) { 117 return da - db; 118 } 119 120 for (Marker am: a.getMarkers()) { 121 for (Marker bm: b.getMarkers()) { 122 if (am != null 123 && bm != null 124 && !Util.isBlank(am.getLocation()) 125 && am.getLocation().equals(bm.getLocation())) { 126 127 int lineDiff = am.getLine() - bm.getLine(); 128 if (lineDiff != 0) { 129 return lineDiff; 130 } 131 int colDiff = am.getColumn() - bm.getColumn(); 132 if (colDiff != 0) { 133 return colDiff; 134 } 135 } 136 } 137 } 138 139 return NamedElementComparator.INSTANCE.compare(a, b); 140 } 141 142 /** 143 * All sources for this target with distances. 144 * @param source 145 * @param target 146 * @param traversed 147 * @return 148 */ 149 private static Map<FlowElement<?>, Integer> sources(FlowElement<?> target, Set<FlowElement<?>> traversed) { 150 Map<FlowElement<?>, Integer> ret = new HashMap<>(); 151 if (traversed.add(target)) { 152 for (Transition input: target.getInputs()) { 153 FlowElement<?> source = (FlowElement<?>) input.eContainer().eContainer(); 154 ret.put(source, 1); 155 for (Entry<FlowElement<?>, Integer> se: sources(source, traversed).entrySet()) { 156 Integer existingDistance = ret.get(se.getKey()); 157 if (existingDistance == null) { 158 ret.put(se.getKey(), se.getValue() + 1); 159 } else if (se.getValue() < existingDistance) { 160 ret.put(se.getKey(), se.getValue()); 161 } 162 } 163 } 164 165 for (Call invocation: target.getInvocations()) { 166 FlowElement<?> source = (FlowElement<?>) invocation.eContainer().eContainer(); 167 ret.put(source, 1); 168 for (Entry<FlowElement<?>, Integer> se: sources(source, traversed).entrySet()) { 169 Integer existingDistance = ret.get(se.getKey()); 170 if (existingDistance == null) { 171 ret.put(se.getKey(), se.getValue() + 1); 172 } else if (se.getValue() < existingDistance) { 173 ret.put(se.getKey(), se.getValue()); 174 } 175 } 176 } 177 } 178 return ret; 179 } 180 181 /** 182 * 183 * @param source 184 * @param target 185 * @param traversed 186 * @return Number of transitions/calls between the source and the target. -1 if target is not reacheable from the source. 187 */ 188 private static int distance(FlowElement<?> source, FlowElement<?> target, Set<FlowElement<?>> traversed) { 189 if (source == target) { 190 return 0; 191 } 192 int distance = -1; 193 if (traversed.add(source)) { 194 for (Transition output: source.getOutputs().values()) { 195 int targetDistance = distance(output.getTarget(), target, traversed); 196 if (targetDistance != -1) { 197 if (distance == -1) { 198 distance = targetDistance + 1; 199 } else { 200 distance = Math.min(distance, targetDistance + 1); 201 } 202 } 203 } 204 for (Call call: source.getCalls().values()) { 205 int targetDistance = distance(call.getTarget(), target, traversed); 206 if (targetDistance != -1) { 207 if (distance == -1) { 208 distance = targetDistance + 1; 209 } else { 210 distance = Math.min(distance, targetDistance + 1); 211 } 212 } 213 } 214 } 215 return distance; 216 } 217 218 @Override 219 protected void populateRepresentation(Diagram representation, FlowStateDiagramGenerator flowStateDiagramGenerator) { 220 if (getTarget().isPartition()) { 221 super.populateRepresentation(representation, flowStateDiagramGenerator); 222 } else { 223 flowStateDiagramGenerator.generateDiagram(getTarget(), representation, null); 224 } 225 } 226 227 @Override 228 protected void resolve( 229 Action action, 230 org.nasdanika.html.emf.EObjectActionResolver.Context context, 231 ProgressMonitor progressMonitor) throws Exception { 232 super.resolve(action, context, progressMonitor); 233 234 if (getTarget().eContainmentFeature() == FlowPackage.Literals.ACTIVITY_ENTRY__VALUE && getTarget().eContainer().eContainmentFeature() == FlowPackage.Literals.SERVICE_PROVIDER__SERVICES) { 235 // Left content panel with children. 236 NavigationPanel leftNavigation = action.getLeftNavigation(); 237 if (leftNavigation == null) { 238 leftNavigation = AppFactory.eINSTANCE.createNavigationPanel(); 239 action.setLeftNavigation(leftNavigation); 240 int subChildren = getTarget().getElements().values().stream().filter(Flow.class::isInstance).map(Flow.class::cast).map(Flow::getElements).mapToInt(Collection::size).sum(); 241 leftNavigation.setStyle(subChildren == 0 ? NavigationPanelStyle.AUTO : flowElements(getTarget()) > 25 ? NavigationPanelStyle.SEARCHABLE_TREE : NavigationPanelStyle.TREE); 242 } 243 Predicate<FlowElement<?>> isPseudoState= PseudoState.class::isInstance; 244 for (FlowElement<?> element: getTarget().getElements().values().stream().filter(isPseudoState.negate()).sorted(FlowActionBuilder::compareFlowElements).collect(Collectors.toList())) { 245 leftNavigation.getItems().add(createItem(action, context, progressMonitor, element)); 246 } 247 } 248 } 249 250 /** 251 * Recursively calculates number of flow elements excluding the flow itself 252 * @param flow 253 * @return 254 */ 255 private static int flowElements(Flow flow) { 256 return flow.getElements().stream().mapToInt(e -> e instanceof Flow ? flowElements((Flow) e) + 1 : 1).sum(); 257 } 258 259 private EObject createItem( 260 Action action, 261 org.nasdanika.html.emf.EObjectActionResolver.Context context, 262 ProgressMonitor progressMonitor, 263 FlowElement<?> element) throws Exception { 264 EObject item = renderValue(action, FlowPackage.Literals.FLOW__ELEMENTS, element, context, progressMonitor); 265 if (element instanceof Flow && item instanceof Label) { 266 Predicate<FlowElement<?>> isPseudoState= PseudoState.class::isInstance; 267 for (FlowElement<?> child: ((Flow) element).getElements().values().stream().filter(isPseudoState.negate()).sorted(FlowActionBuilder::compareFlowElements).collect(Collectors.toList())) { 268 ((Label) item).getChildren().add(createItem(action, context, progressMonitor, child)); 269 } 270 } 271 return item; 272 } 273 274}