1227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks/*
2227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks * Copyright (C) 2012 The Android Open Source Project
3227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks *
4227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks * Licensed under the Apache License, Version 2.0 (the "License");
5227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks * you may not use this file except in compliance with the License.
6227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks * You may obtain a copy of the License at
7227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks *
8227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks *      http://www.apache.org/licenses/LICENSE-2.0
9227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks *
10227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks * Unless required by applicable law or agreed to in writing, software
11227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks * distributed under the License is distributed on an "AS IS" BASIS,
12227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks * See the License for the specific language governing permissions and
14227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks * limitations under the License.
15227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks */
16227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
17227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks// This class provides functions to export a FilterGraph.
18227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
19227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendrickspackage androidx.media.filterfw;
20227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
21227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricksimport android.content.Context;
22227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
23227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricksimport java.io.FileOutputStream;
24227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricksimport java.io.OutputStreamWriter;
25227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricksimport java.util.HashMap;
26227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricksimport java.util.HashSet;
27227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricksimport java.util.Map.Entry;
28227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricksimport java.util.Set;
29227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
30227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks/**
31227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks * This class provides functions to export a FilterGraph as a DOT file.
32227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks */
33227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendrickspublic class GraphExporter {
34227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
35227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    /**
36227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks     * Exports the graph as DOT (see http://en.wikipedia.org/wiki/DOT_language).
37227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks     * Using the exported file, the graph can be visualized e.g. with the command line tool dot.
38227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks     * Optionally, one may /exclude/ unconnected optional ports (third parameter = false),
39227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks     * since they can quickly clutter the visualization (and, depending on the purpose, may not
40227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks     * be interesting).
41227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks     *
42227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks     * Example workflow:
43227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks     *  1. run application on device, make sure it calls exportGraphAsDOT(...);
44227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks     *  2. adb pull /data/data/<application name>/files/<graph filename>.gv graph.gv
45227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks     *  3. dot -Tpng graph.gv -o graph.png
46227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks     *  4. eog graph.png
47227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks     */
48227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    static public void exportAsDot(FilterGraph graph, String filename,
49227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            boolean includeUnconnectedOptionalPorts)
50227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            throws java.io.FileNotFoundException, java.io.IOException {
51227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        // Initialize, open file stream
52227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        Context myAppContext = graph.getContext().getApplicationContext();
53227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        Filter[] filters = graph.getAllFilters();
54227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        FileOutputStream fOut = myAppContext.openFileOutput(filename, Context.MODE_PRIVATE);
55227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        OutputStreamWriter dotFile = new OutputStreamWriter(fOut);
56227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
57227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        // Write beginning of DOT file
58227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        dotFile.write("digraph graphname {\n");
59227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        dotFile.write("  node [shape=record];\n");
60227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
61227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        // N.B. For specification and lots of examples of the DOT language, see
62227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        //   http://www.graphviz.org/Documentation/dotguide.pdf
63227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
64227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        // Iterate over all filters of the graph, write corresponding DOT node elements
65227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
66227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        for(Filter filter : filters) {
67227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            dotFile.write(getDotName("  " + filter.getName()) + " [label=\"{");
68227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
69227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            // Write upper part of element (i.e., input ports)
70227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            Set<String> inputPorts = getInputPorts(filter, includeUnconnectedOptionalPorts);
71227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            if(inputPorts.size() > 0) {
72227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                dotFile.write(" { ");
73227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                int counter = 0;
74227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                for(String p : inputPorts) {
75227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    dotFile.write("<" + getDotName(p) + "_IN>" + p);
76227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    if(++counter != inputPorts.size()) dotFile.write(" | ");
77227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                }
78227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                dotFile.write(" } | ");
79227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            }
80227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
81227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            // Write center part of element (i.e., element label)
82227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            dotFile.write(filter.getName());
83227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
84227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            // Write lower part of element (i.e., output ports)
85227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            Set<String> outputPorts = getOutputPorts(filter, includeUnconnectedOptionalPorts);
86227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            if(outputPorts.size() > 0) {
87227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                dotFile.write(" | { ");
88227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                int counter = 0;
89227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                for(String p : outputPorts) {
90227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    dotFile.write("<" + getDotName(p) + "_OUT>" + p);
91227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    if(++counter != outputPorts.size()) dotFile.write(" | ");
92227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                }
93227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                dotFile.write(" } ");
94227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            }
95227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
96227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            dotFile.write("}\"];\n");
97227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        }
98227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        dotFile.write("\n");
99227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
100227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        // Iterate over all filters again to collect connections and find unconnected ports
101227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
102227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        int dummyNodeCounter = 0;
103227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        for(Filter filter : filters) {
104227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            Set<String> outputPorts = getOutputPorts(filter, includeUnconnectedOptionalPorts);
105227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            for(String portName : outputPorts) {
106227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                OutputPort source = filter.getConnectedOutputPort(portName);
107227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                if(source != null) {
108227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    // Found a connection, draw it
109227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    InputPort target = source.getTarget();
110227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    dotFile.write("  " +
111227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        getDotName(source.getFilter().getName()) + ":" +
112227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        getDotName(source.getName()) + "_OUT -> " +
113227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        getDotName(target.getFilter().getName()) + ":" +
114227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        getDotName(target.getName()) + "_IN;\n" );
115227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                } else {
116227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    // Found a unconnected output port, add dummy node
117227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    String color = filter.getSignature().getOutputPortInfo(portName).isRequired()
118227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        ? "red" : "blue";  // red for unconnected, required ports
119227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    dotFile.write("  " +
120227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        "dummy" + (++dummyNodeCounter) +
121227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        " [shape=point,label=\"\",color=" + color + "];\n" +
122227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        "  " + getDotName(filter.getName()) + ":" +
123227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        getDotName(portName) + "_OUT -> " +
124227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        "dummy" + dummyNodeCounter + " [color=" + color + "];\n");
125227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                }
126227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            }
127227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
128227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            Set<String> inputPorts = getInputPorts(filter, includeUnconnectedOptionalPorts);
129227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            for(String portName : inputPorts) {
130227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                InputPort target = filter.getConnectedInputPort(portName);
131227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                if(target != null) {
132227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    // Found a connection -- nothing to do, connections have been written out above
133227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                } else {
134227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    // Found a unconnected input port, add dummy node
135227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    String color = filter.getSignature().getInputPortInfo(portName).isRequired()
136227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        ? "red" : "blue";  // red for unconnected, required ports
137227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    dotFile.write("  " +
138227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        "dummy" + (++dummyNodeCounter) +
139227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        " [shape=point,label=\"\",color=" + color + "];\n" +
140227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        "  dummy" + dummyNodeCounter + " -> " +
141227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        getDotName(filter.getName()) + ":" +
142227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                        getDotName(portName) + "_IN [color=" + color + "];\n");
143227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                }
144227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            }
145227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        }
146227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
147227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        // Write end of DOT file, close file stream
148227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        dotFile.write("}\n");
149227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        dotFile.flush();
150227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        dotFile.close();
151227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    }
152227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
153227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    // Internal methods
154227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
155227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    // From element's name in XML, create DOT-allowed element name
156227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    static private String getDotName(String raw) {
157227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        return raw.replaceAll("\\.", "___"); // DOT does not allow . in element names
158227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    }
159227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
160227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    // Retrieve all input ports of a filter, including:
161227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    //  unconnected ports (which can not be retrieved from the filter, only from the signature), and
162227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    //  additional (connected) ports not listed in the signature (which is allowed by default,
163227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    //    unless disallowOtherInputs is defined in signature).
164227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    // With second parameter = false, *omit* unconnected optional ports.
165227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    static private Set<String> getInputPorts(Filter filter, boolean includeUnconnectedOptional) {
166227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        // add (connected) ports from filter
167227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        Set<String> ports = new HashSet<String>();
168227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        ports.addAll(filter.getConnectedInputPortMap().keySet());
169227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
170227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        // add (unconnected) ports from signature
171227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        HashMap<String, Signature.PortInfo> signaturePorts = filter.getSignature().getInputPorts();
172227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        if(signaturePorts != null){
173227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            for(Entry<String, Signature.PortInfo> e : signaturePorts.entrySet()) {
174227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                if(includeUnconnectedOptional || e.getValue().isRequired()) {
175227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    ports.add(e.getKey());
176227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                }
177227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            }
178227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        }
179227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        return ports;
180227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    }
181227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
182227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    // Retrieve all output ports of a filter (analogous to above function)
183227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    static private Set<String> getOutputPorts(Filter filter, boolean includeUnconnectedOptional) {
184227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        // add (connected) ports from filter
185227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        Set<String> ports = new HashSet<String>();
186227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        ports.addAll(filter.getConnectedOutputPortMap().keySet());
187227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks
188227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        // add (unconnected) ports from signature
189227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        HashMap<String, Signature.PortInfo> signaturePorts = filter.getSignature().getOutputPorts();
190227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        if(signaturePorts != null){
191227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            for(Entry<String, Signature.PortInfo> e : signaturePorts.entrySet()) {
192227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                if(includeUnconnectedOptional || e.getValue().isRequired()) {
193227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                    ports.add(e.getKey());
194227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks                }
195227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks            }
196227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        }
197227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks        return ports;
198227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks    }
199227b47625d7482b5b47ad0e4c70ce0a246236adeBenjamin Hendricks}
200