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    }