001 /*--------------------------------------------------------------------------+ 002 $Id: SimulinkUtils.java 26277 2010-02-18 10:46:58Z juergens $ 003 | | 004 | Copyright 2005-2010 Technische Universitaet Muenchen | 005 | | 006 | Licensed under the Apache License, Version 2.0 (the "License"); | 007 | you may not use this file except in compliance with the License. | 008 | You may obtain a copy of the License at | 009 | | 010 | http://www.apache.org/licenses/LICENSE-2.0 | 011 | | 012 | Unless required by applicable law or agreed to in writing, software | 013 | distributed under the License is distributed on an "AS IS" BASIS, | 014 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 015 | See the License for the specific language governing permissions and | 016 | limitations under the License. | 017 +--------------------------------------------------------------------------*/ 018 package edu.tum.cs.simulink.util; 019 020 import java.util.ArrayList; 021 import java.util.Collection; 022 import java.util.HashMap; 023 import java.util.Iterator; 024 import java.util.List; 025 import java.util.Map; 026 import java.util.Set; 027 import java.util.regex.Matcher; 028 import java.util.regex.Pattern; 029 030 import edu.tum.cs.commons.assertion.CCSMAssert; 031 import edu.tum.cs.commons.assertion.CCSMPre; 032 import edu.tum.cs.commons.assertion.PreconditionException; 033 import edu.tum.cs.commons.collections.IdentityHashSet; 034 import edu.tum.cs.commons.error.NeverThrownRuntimeException; 035 import edu.tum.cs.commons.string.StringUtils; 036 import edu.tum.cs.commons.visitor.IVisitor; 037 import edu.tum.cs.simulink.model.ParameterizedElement; 038 import edu.tum.cs.simulink.model.SimulinkBlock; 039 import edu.tum.cs.simulink.model.SimulinkConstants; 040 import edu.tum.cs.simulink.model.SimulinkInPort; 041 import edu.tum.cs.simulink.model.SimulinkModel; 042 import edu.tum.cs.simulink.model.SimulinkOutPort; 043 import edu.tum.cs.simulink.model.stateflow.IStateflowElement; 044 import edu.tum.cs.simulink.model.stateflow.IStateflowNodeContainer; 045 import edu.tum.cs.simulink.model.stateflow.StateflowBlock; 046 import edu.tum.cs.simulink.model.stateflow.StateflowChart; 047 import edu.tum.cs.simulink.model.stateflow.StateflowMachine; 048 import edu.tum.cs.simulink.model.stateflow.StateflowNodeBase; 049 import edu.tum.cs.simulink.model.stateflow.StateflowState; 050 import edu.tum.cs.simulink.model.stateflow.StateflowTarget; 051 052 /** 053 * Collection of utility methods for Simulink models. 054 * 055 * @author deissenb 056 * @author $Author: juergens $ 057 * @version $Rev: 26277 $ 058 * @levd.rating GREEN Hash: 0D8577FED291C7F4F560DB259060F6F4 059 */ 060 public class SimulinkUtils { 061 062 /** Visitor that stores all blocks in a id->block map. */ 063 private static class MapVisitor implements 064 IVisitor<SimulinkBlock, NeverThrownRuntimeException> { 065 /** Maps from block id to block. */ 066 private final HashMap<String, SimulinkBlock> map = new HashMap<String, SimulinkBlock>(); 067 068 /** Visit block */ 069 public void visit(SimulinkBlock block) { 070 map.put(block.getId(), block); 071 } 072 } 073 074 /** Copy parameters from one parameterized element to another. */ 075 public static void copyParameters(ParameterizedElement source, 076 ParameterizedElement target) { 077 for (String name : source.getParameterNames()) { 078 target.setParameter(name, source.getParameter(name)); 079 } 080 } 081 082 /** Create map that maps from id to block. */ 083 public static Map<String, SimulinkBlock> createIdToNodeMap( 084 SimulinkBlock block) { 085 MapVisitor visitor = new MapVisitor(); 086 visitDepthFirst(block, visitor); 087 return visitor.map; 088 } 089 090 /** Replaces forward slashes by double forward slashes. */ 091 public static String escape(String string) { 092 return string.replace("/", "//"); 093 } 094 095 /** 096 * Get Simulink array parameter as array. This raises a 097 * {@link NumberFormatException} if the elements of the array are not 098 * integers. 099 */ 100 public static int[] getIntParameterArray(String parameter) { 101 String[] parts = getStringParameterArray(parameter); 102 int[] result = new int[parts.length]; 103 for (int i = 0; i < result.length; i++) { 104 result[i] = Integer.parseInt(parts[i]); 105 } 106 return result; 107 } 108 109 /** Get Simulink array parameter as array. */ 110 public static String[] getStringParameterArray(String parameter) { 111 // remove brackets 112 String content = parameter.substring(1, parameter.length() - 1); 113 if (StringUtils.isEmpty(content)) { 114 return new String[0]; 115 } 116 return content.split("[,;] *"); 117 } 118 119 /** Checks if a block is a target link block. */ 120 public static boolean isTargetlinkBlock(SimulinkBlock node) { 121 return node.getType().equals(SimulinkConstants.TYPE_Reference) 122 && node.getParameter(SimulinkConstants.PARAM_SourceType) 123 .startsWith("TL_"); 124 } 125 126 /** Split full qualified identifier. */ 127 public static List<String> splitSimulinkId(String string) { 128 ArrayList<String> result = new ArrayList<String>(); 129 130 // Simulink names cannot start or end with a slash 131 Pattern pattern = Pattern.compile("[^/]/[^/]"); 132 Matcher matcher = pattern.matcher(string); 133 134 int begin = 0; 135 while (matcher.find(begin)) { 136 result.add(removeEscapes(string.substring(begin, 137 matcher.start() + 1))); 138 // pattern is one character longer than the slash 139 begin = matcher.end() - 1; 140 } 141 result.add(removeEscapes(string.substring(begin))); 142 143 return result; 144 } 145 146 /** 147 * Create Simulink id from a iteration of names. This takes care of proper 148 * escaping. 149 * 150 * @throws PreconditionException 151 * if one of names starts or ends with a slash 152 */ 153 public static String createSimulinkId(Iterable<String> names) { 154 StringBuilder result = new StringBuilder(); 155 Iterator<String> it = names.iterator(); 156 while (it.hasNext()) { 157 String name = it.next(); 158 CCSMPre.isFalse(name.startsWith("/") || name.endsWith("/"), 159 "Simulink names cannot start or end with a slash."); 160 result.append(escape(name)); 161 if (it.hasNext()) { 162 result.append("/"); 163 } 164 } 165 return result.toString(); 166 } 167 168 /** 169 * Visit blocks in a depth first manner. 170 * 171 * @param <X> 172 * Type of exception thrown by the visitor. 173 * @param block 174 * block to start with 175 * @param visitor 176 * the visitor 177 * @throws X 178 * exception thrown by the visitor. 179 */ 180 public static <X extends Exception> void visitDepthFirst( 181 SimulinkBlock block, IVisitor<SimulinkBlock, X> visitor) throws X { 182 visitor.visit(block); 183 if (!block.hasSubBlocks()) { 184 return; 185 } 186 for (SimulinkBlock child : block.getSubBlocks()) { 187 visitDepthFirst(child, visitor); 188 } 189 } 190 191 /** Replace double forward slashes by single forward slashes */ 192 private static String removeEscapes(String name) { 193 return name.replace("//", "/"); 194 } 195 196 /** Returns all recursively reachable subblocks of the given block. */ 197 public static List<SimulinkBlock> listBlocksDepthFirst(SimulinkBlock block) { 198 final List<SimulinkBlock> result = new ArrayList<SimulinkBlock>(); 199 SimulinkUtils.visitDepthFirst(block, 200 new IVisitor<SimulinkBlock, NeverThrownRuntimeException>() { 201 public void visit(SimulinkBlock block) { 202 result.add(block); 203 } 204 }); 205 return result; 206 } 207 208 /** 209 * Calculate the set of all parent blocks up to the model for the given 210 * blocks. 211 */ 212 public static Set<SimulinkBlock> calculateParentSet( 213 Collection<SimulinkBlock> blocks) { 214 215 Set<SimulinkBlock> parents = new IdentityHashSet<SimulinkBlock>(); 216 if (blocks.isEmpty()) { 217 return parents; 218 } 219 220 for (SimulinkBlock block : blocks) { 221 SimulinkModel model = block.getModel(); 222 while (block != model) { 223 parents.add(block); 224 block = block.getParent(); 225 } 226 } 227 228 return parents; 229 } 230 231 /** Recursively count sub blocks. */ 232 public static int countSubBlocks(SimulinkBlock block) { 233 BlockCounter counter = new BlockCounter(); 234 SimulinkUtils.visitDepthFirst(block, counter); 235 // minus the root block 236 return counter.blockCount - 1; 237 } 238 239 /** Recursively count lines. */ 240 public static int countLines(SimulinkBlock block) { 241 BlockCounter counter = new BlockCounter(); 242 for (SimulinkBlock child : block.getSubBlocks()) { 243 SimulinkUtils.visitDepthFirst(child, counter); 244 } 245 return counter.lineCount; 246 } 247 248 /** Recursively count Stateflow states. */ 249 public static int countStates(IStateflowNodeContainer<?> node) { 250 int count = 0; 251 if (node instanceof StateflowState) { 252 count = 1; 253 } else { 254 count = 0; 255 } 256 257 for (StateflowNodeBase element : node.getNodes()) { 258 if (element instanceof IStateflowNodeContainer<?>) { 259 count += countStates((IStateflowNodeContainer<?>) element); 260 } 261 } 262 return count; 263 } 264 265 /** Count states of all charts of the machine. */ 266 public static int countStates(StateflowMachine stateflowMachine) { 267 int stateCount = 0; 268 for (StateflowChart chart : stateflowMachine.getCharts()) { 269 stateCount += countStates(chart); 270 } 271 return stateCount; 272 } 273 274 /** 275 * Get the Stateflow chart a Stateflow element belongs to. 276 * 277 * @return the Stateflow chart or <code>null</code> if the element is 278 * unconnected or not associated with a chart, e.g. 279 * {@link StateflowTarget}. 280 */ 281 public static StateflowChart getChart(IStateflowElement<?> element) { 282 if (element instanceof StateflowChart) { 283 return (StateflowChart) element; 284 } 285 IStateflowElement<?> parent = element.getParent(); 286 if (parent == null) { 287 return null; 288 } 289 return getChart(parent); 290 } 291 292 /** 293 * Get the Stateflow block a Stateflow element belongs to. 294 * 295 * @return the Stateflow block or <code>null</code> if the element is 296 * unconnected or not associated with a chart, e.g. 297 * {@link StateflowTarget}. 298 */ 299 public static StateflowBlock getBlock(IStateflowElement<?> element) { 300 StateflowChart chart = getChart(element); 301 if (chart == null) { 302 return null; 303 } 304 return chart.getStateflowBlock(); 305 } 306 307 /** 308 * Get name of a Stateflow state as defined in the Stateflow manual. As 309 * Stateflow awkwardly stores the names as part of the label, this is put in 310 * a utility methods and not directly at class {@link StateflowState}. 311 */ 312 public static String getStateName(StateflowState state) { 313 String label = state.getLabel(); 314 if (StringUtils.isEmpty(label)) { 315 return null; 316 } 317 String name = label.split("\\\\n")[0]; 318 319 // State names MAY end with a slash 320 if (name.length() > 1 && name.endsWith("/")) { 321 name = name.substring(0, name.length() - 1); 322 } 323 return name; 324 } 325 326 /** 327 * Get full qualified state name. This is deliberately not part of class 328 * {@link StateflowState} as names of Stateflow derives names from the state 329 * labels. 330 */ 331 public static String getFQStateName(StateflowState state) { 332 String name = getStateName(state); 333 IStateflowNodeContainer<?> parent = state.getParent(); 334 if (parent == null) { 335 return name; 336 } 337 if (parent instanceof StateflowChart) { 338 StateflowChart chart = (StateflowChart) parent; 339 return chart.getStateflowBlock().getId() + "/" + name; 340 } 341 342 // Can be only a state 343 return getFQStateName((StateflowState) parent) + "." + name; 344 } 345 346 /** 347 * Obtain out port block that is below the a Stateflow block and describes 348 * the output of a Stateflow chart. 349 * 350 * What Simulink displays like an atomic Stateflow chart is internally 351 * represented as a Simulink sub system that itself contains multiple 352 * blocks. The sub system itself has a normal inport/outport which has only 353 * a number (as (almost) all ports of Simulink sub systems do). However the 354 * sub system contains a block of <b>type</b> Inport/Outport (quite 355 * confusing...) and this is the one the carries the name of the Stateflow 356 * output. Note that this is related to a past CR described at 357 * <https://bugzilla.informatik.tu-muenchen.de/show_bug.cgi?id=1502>. 358 * 359 * The code is the following: 360 * <ul> 361 * <li>iterate over all child blocks of the sub system that represents the 362 * Stateflow chart 363 * <li>pick the one that is of type Inport/Outport and that has the same 364 * port index as the inport/outport of the sub system (the index defines the 365 * mapping between the Inport/Ouport block and the actual inport/outport) 366 * </ul> 367 */ 368 public static SimulinkBlock getStateflowOutport(SimulinkOutPort outPort) { 369 CCSMPre.isInstanceOf(outPort.getBlock(), StateflowBlock.class); 370 SimulinkBlock result = null; 371 for (SimulinkBlock block : outPort.getBlock().getSubBlocks()) { 372 if (SimulinkConstants.TYPE_Outport.equals(block.getType()) 373 && block.getParameter(SimulinkConstants.PARAM_Port).equals( 374 outPort.getIndex())) { 375 CCSMAssert.isTrue(result == null, 376 "We assummed that there is only one matching port."); 377 result = block; 378 } 379 } 380 return result; 381 } 382 383 /** 384 * Obtain in port. See {@link #getStateflowOutport(SimulinkOutPort)} for 385 * details. 386 */ 387 public static SimulinkBlock getStateflowInport(SimulinkInPort inPort) { 388 CCSMPre.isInstanceOf(inPort.getBlock(), StateflowBlock.class); 389 SimulinkBlock result = null; 390 for (SimulinkBlock block : inPort.getBlock().getSubBlocks()) { 391 if (SimulinkConstants.TYPE_Inport.equals(block.getType()) 392 && block.getParameter(SimulinkConstants.PARAM_Port).equals( 393 inPort.getIndex())) { 394 CCSMAssert.isTrue(result == null, 395 "We assummed that there is only one matching port."); 396 result = block; 397 } 398 } 399 return result; 400 } 401 402 /** Visitor for counting sub blocks. */ 403 private static class BlockCounter implements 404 IVisitor<SimulinkBlock, NeverThrownRuntimeException> { 405 /** Counter for blocks. */ 406 private int blockCount = 0; 407 408 /** Counter for lines. */ 409 private int lineCount = 0; 410 411 /** Count block. */ 412 public void visit(SimulinkBlock element) { 413 blockCount++; 414 lineCount += element.getOutLines().size(); 415 } 416 417 } 418 }