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 Marker am = a.getMarker(); 121 Marker bm = b.getMarker(); 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 return NamedElementComparator.INSTANCE.compare(a, b); 138 } 139 140 /** 141 * All sources for this target with distances. 142 * @param source 143 * @param target 144 * @param traversed 145 * @return 146 */ 147 private static Map<FlowElement<?>, Integer> sources(FlowElement<?> target, Set<FlowElement<?>> traversed) { 148 Map<FlowElement<?>, Integer> ret = new HashMap<>(); 149 if (traversed.add(target)) { 150 for (Transition input: target.getInputs()) { 151 FlowElement<?> source = (FlowElement<?>) input.eContainer().eContainer(); 152 ret.put(source, 1); 153 for (Entry<FlowElement<?>, Integer> se: sources(source, traversed).entrySet()) { 154 Integer existingDistance = ret.get(se.getKey()); 155 if (existingDistance == null) { 156 ret.put(se.getKey(), se.getValue() + 1); 157 } else if (se.getValue() < existingDistance) { 158 ret.put(se.getKey(), se.getValue()); 159 } 160 } 161 } 162 163 for (Call invocation: target.getInvocations()) { 164 FlowElement<?> source = (FlowElement<?>) invocation.eContainer().eContainer(); 165 ret.put(source, 1); 166 for (Entry<FlowElement<?>, Integer> se: sources(source, traversed).entrySet()) { 167 Integer existingDistance = ret.get(se.getKey()); 168 if (existingDistance == null) { 169 ret.put(se.getKey(), se.getValue() + 1); 170 } else if (se.getValue() < existingDistance) { 171 ret.put(se.getKey(), se.getValue()); 172 } 173 } 174 } 175 } 176 return ret; 177 } 178 179 /** 180 * 181 * @param source 182 * @param target 183 * @param traversed 184 * @return Number of transitions/calls between the source and the target. -1 if target is not reacheable from the source. 185 */ 186 private static int distance(FlowElement<?> source, FlowElement<?> target, Set<FlowElement<?>> traversed) { 187 if (source == target) { 188 return 0; 189 } 190 int distance = -1; 191 if (traversed.add(source)) { 192 for (Transition output: source.getOutputs().values()) { 193 int targetDistance = distance(output.getTarget(), target, traversed); 194 if (targetDistance != -1) { 195 if (distance == -1) { 196 distance = targetDistance + 1; 197 } else { 198 distance = Math.min(distance, targetDistance + 1); 199 } 200 } 201 } 202 for (Call call: source.getCalls().values()) { 203 int targetDistance = distance(call.getTarget(), target, traversed); 204 if (targetDistance != -1) { 205 if (distance == -1) { 206 distance = targetDistance + 1; 207 } else { 208 distance = Math.min(distance, targetDistance + 1); 209 } 210 } 211 } 212 } 213 return distance; 214 } 215 216 @Override 217 protected void populateRepresentation(Diagram representation, FlowStateDiagramGenerator flowStateDiagramGenerator) { 218 if (getTarget().isPartition()) { 219 super.populateRepresentation(representation, flowStateDiagramGenerator); 220 } else { 221 flowStateDiagramGenerator.generateDiagram(getTarget(), representation, null); 222 } 223 } 224 225 @Override 226 protected void resolve( 227 Action action, 228 org.nasdanika.html.emf.EObjectActionResolver.Context context, 229 ProgressMonitor progressMonitor) throws Exception { 230 super.resolve(action, context, progressMonitor); 231 232 if (getTarget().eContainmentFeature() == FlowPackage.Literals.ACTIVITY_ENTRY__VALUE && getTarget().eContainer().eContainmentFeature() == FlowPackage.Literals.SERVICE_PROVIDER__SERVICES) { 233 // Left content panel with children. 234 NavigationPanel leftNavigation = action.getLeftNavigation(); 235 if (leftNavigation == null) { 236 leftNavigation = AppFactory.eINSTANCE.createNavigationPanel(); 237 action.setLeftNavigation(leftNavigation); 238 int subChildren = getTarget().getElements().values().stream().filter(Flow.class::isInstance).map(Flow.class::cast).map(Flow::getElements).mapToInt(Collection::size).sum(); 239 leftNavigation.setStyle(subChildren == 0 ? NavigationPanelStyle.AUTO : NavigationPanelStyle.TREE); 240 } 241 Predicate<FlowElement<?>> isPseudoState= PseudoState.class::isInstance; 242 for (FlowElement<?> element: getTarget().getElements().values().stream().filter(isPseudoState.negate()).sorted(FlowActionBuilder::compareFlowElements).collect(Collectors.toList())) { 243 leftNavigation.getItems().add(createItem(action, context, progressMonitor, element)); 244 } 245 } 246 247 } 248 249 private EObject createItem( 250 Action action, 251 org.nasdanika.html.emf.EObjectActionResolver.Context context, 252 ProgressMonitor progressMonitor, 253 FlowElement<?> element) throws Exception { 254 EObject item = renderValue(action, FlowPackage.Literals.FLOW__ELEMENTS, element, context, progressMonitor); 255 if (element instanceof Flow && item instanceof Label) { 256 Predicate<FlowElement<?>> isPseudoState= PseudoState.class::isInstance; 257 for (FlowElement<?> child: ((Flow) element).getElements().values().stream().filter(isPseudoState.negate()).sorted(FlowActionBuilder::compareFlowElements).collect(Collectors.toList())) { 258 ((Label) item).getChildren().add(createItem(action, context, progressMonitor, child)); 259 } 260 } 261 return item; 262 } 263 264}