From 18a07dd201ec66d34fd282c572bf753479f46be3 Mon Sep 17 00:00:00 2001 From: Tom Rodriguez Date: Mon, 13 Oct 2025 21:32:58 -0700 Subject: [PATCH 01/11] Delete JSONExporter --- .../IdealGraphVisualizer/JSONExporter/pom.xml | 38 -- .../org/graalvm/visualizer/JSONExporter.java | 559 ------------------ .../JSONExporter/src/main/nbm/manifest.mf | 3 - .../visualizer/jsonexporter/Bundle.properties | 6 - visualizer/IdealGraphVisualizer/pom.xml | 1 - 5 files changed, 607 deletions(-) delete mode 100644 visualizer/IdealGraphVisualizer/JSONExporter/pom.xml delete mode 100644 visualizer/IdealGraphVisualizer/JSONExporter/src/main/java/org/graalvm/visualizer/JSONExporter.java delete mode 100644 visualizer/IdealGraphVisualizer/JSONExporter/src/main/nbm/manifest.mf delete mode 100644 visualizer/IdealGraphVisualizer/JSONExporter/src/main/resources/org/graalvm/visualizer/jsonexporter/Bundle.properties diff --git a/visualizer/IdealGraphVisualizer/JSONExporter/pom.xml b/visualizer/IdealGraphVisualizer/JSONExporter/pom.xml deleted file mode 100644 index 49e3e2777bdc..000000000000 --- a/visualizer/IdealGraphVisualizer/JSONExporter/pom.xml +++ /dev/null @@ -1,38 +0,0 @@ - - 4.0.0 - - org.graalvm.visualizer - IdealGraphVisualizer-parent - 1.24-SNAPSHOT - - JSONExporter - nbm - - - - org.apache.netbeans.utilities - nbm-maven-plugin - true - - - org.apache.maven.plugins - maven-jar-plugin - - - ${project.build.outputDirectory}/META-INF/MANIFEST.MF - - - - - - - - ${project.groupId} - Data - ${project.version} - - - - UTF-8 - - diff --git a/visualizer/IdealGraphVisualizer/JSONExporter/src/main/java/org/graalvm/visualizer/JSONExporter.java b/visualizer/IdealGraphVisualizer/JSONExporter/src/main/java/org/graalvm/visualizer/JSONExporter.java deleted file mode 100644 index fde4941f29fb..000000000000 --- a/visualizer/IdealGraphVisualizer/JSONExporter/src/main/java/org/graalvm/visualizer/JSONExporter.java +++ /dev/null @@ -1,559 +0,0 @@ -/* - * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package org.graalvm.visualizer; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URL; -import java.nio.file.Paths; -import java.util.*; - -import jdk.graal.compiler.graphio.parsing.*; -import jdk.graal.compiler.graphio.parsing.model.*; - -public class JSONExporter { - - private static final String ID = "id"; - private static final String NAME = "name"; - private static final String GRAPH_TYPE = "graph_type"; - private static final String PART = "part"; - - private static final String NODES = "nodes"; - private static final String EDGES = "edges"; - private static final String BLOCKS = "blocks"; - - private static final String JAVA = "Java"; - - private static final int JSON_SIZE_LIMIT = (int) Math.pow(2, 28); // ~270 MB per json file - - public static void main(String[] args) throws IOException { - if (args.length == 0 || !args[0].endsWith(".bgv") || args[0].contentEquals("-h") || args[0].contentEquals("--help")) { - printHelp(); - return; - } - boolean addBlocks = false; - List graphNames = new ArrayList<>(); - if (args.length > 1) { - int i = 1; - while (i < args.length) { - switch (args[i]) { - case "-b": - addBlocks = true; - break; - case "-n": - if (++i == args.length) { - printHelp(); - return; - } - String gn = args[i]; - if (gn.length() > 3 && (gn.charAt(0) == '"' || gn.charAt(0) == '\'')) { - gn = gn.substring(1, gn.length() - 1); - } - graphNames.add(gn.toLowerCase()); - break; - default: - printHelp(); - return; - } - i++; - } - } - exportToJSON(args[0], graphNames, addBlocks); - } - - private static void printHelp() { - System.out.println("usage:"); - System.out.println(" mx igv-json [-b][-n GRAPH_NAME]"); - } - - @SuppressWarnings("deprecation") - private static void fillGraphsList(URL url, List graphs) throws IOException { - ModelBuilder mb = new ModelBuilder(new GraphDocument(), null) { - @Override - public InputGraph startGraph(int dumpId, String format, Object[] args) { - InputGraph g = super.startGraph(dumpId, format, args); - graphs.add(g); - return g; - } - }; - new BinaryReader(new StreamSource(url.openStream()), mb).parse(); - } - - private static boolean isGraphNameAccepted(List graphNames, String n) { - for (String name : graphNames) { - if (n.toLowerCase().contains(name)) { - return true; - } - } - return false; - } - - private static JSONHelper.JSONObjectBuilder createRoot(int dumpID, String graphType, String graphName) { - JSONHelper.JSONObjectBuilder root = JSONHelper.object(); - root.add(ID, dumpID); - root.add(NAME, graphName); - root.add(GRAPH_TYPE, graphType); - return root; - } - - public static void exportToJSON(String in, List graphNames, boolean addBlocks) throws IOException { - URL url = new URL(new URL("file:"), in); - List graphs = new ArrayList<>(); - fillGraphsList(url, graphs); - for (InputGraph graph : graphs) { - if (graph.getProperties().size() <= 1) { - // This is most likely an IR graph that is not needed - continue; - } - String graphName = graph.getProperties().get(NAME, String.class); - if (graphNames.size() > 0 && !isGraphNameAccepted(graphNames, graphName)) { - continue; - } - String graphType = graph.getGraphType(); - int dumpID = graph.getDumpId(); - int currentPart = -1; - JSONHelper.JSONObjectBuilder root = createRoot(dumpID, graphType, graphName); - JSONHelper.JSONReadyObject nodesJson = JSONHelper.readyObject(0); - for (InputNode node : graph.getNodes()) { - if (nodesJson.getSize() > JSON_SIZE_LIMIT) { - root.add(NODES, nodesJson); - root.add(PART, ++currentPart); - write(in, graphType, graphName, currentPart, root.toString()); - root = createRoot(dumpID, graphType, graphName); - nodesJson = JSONHelper.readyObject(0); - } - nodesJson.add(node.getId() + "", getNode(node)); - } - root.add(NODES, nodesJson); - - JSONHelper.JSONReadyArray edgesJson = JSONHelper.readyArray(nodesJson.getSize()); - for (InputEdge e : graph.getEdges()) { - if (edgesJson.getSize() > JSON_SIZE_LIMIT) { - root.add(EDGES, edgesJson); - root.add(PART, ++currentPart); - write(in, graphType, graphName, currentPart, root.toString()); - root = createRoot(dumpID, graphType, graphName); - edgesJson = JSONHelper.readyArray(0); - } - edgesJson.add(getEdge(e)); - } - root.add(EDGES, edgesJson); - - if (addBlocks) { - /* Blocks can be reconstructed using nodes properties */ - JSONHelper.JSONReadyObject blocksJson = JSONHelper.readyObject(edgesJson.getSize()); - for (InputBlock b : graph.getBlocks()) { - if (edgesJson.getSize() > JSON_SIZE_LIMIT) { - root.add(BLOCKS, blocksJson); - root.add(PART, ++currentPart); - write(in, graphType, graphName, currentPart, root.toString()); - root = createRoot(dumpID, graphType, graphName); - edgesJson = JSONHelper.readyArray(0); - } - blocksJson.add(b.getName(), getBlock(b)); - } - root.add(BLOCKS, blocksJson); - } - - if (currentPart > -1) { - root.add(PART, ++currentPart); - } - write(in, graphType, graphName, currentPart, root.toString()); - } - } - - private static void write(String in, String graphType, String graphName, int part, String content) throws IOException { - String out = in.substring(0, in.lastIndexOf('.')) + '_'; - String outputPath = Paths.get(out + createFileName(graphType, graphName, part)).toString(); - System.out.println("Exporting to " + outputPath); - try (PrintWriter writer = new PrintWriter(new File(outputPath))) { - writer.write(content); - } - } - - private static JSONHelper.JSONObjectBuilder getNode(InputNode node) { - JSONHelper.JSONObjectBuilder nodeJson = JSONHelper.object(); - for (Property p : node.getProperties()) { - Object o = p.getValue(); - if (o != null) { - String key = p.getName(); - if (o instanceof LocationStackFrame) { - nodeJson.add(key, stacktrace(((LocationStackFrame) o))); - } else { - nodeJson.add(key, o.toString()); - } - } - } - return nodeJson; - } - - public static JSONHelper.JSONArrayBuilder getEdge(InputEdge edge) { - JSONHelper.JSONArrayBuilder e = JSONHelper.array(); - e.add(edge.getFrom()).add(edge.getTo()); - e.add(edge.getLabel()).add(edge.getType()); - return e; - } - - public static JSONHelper.JSONObjectBuilder getBlock(InputBlock b) { - JSONHelper.JSONObjectBuilder block = JSONHelper.object(); - JSONHelper.JSONArrayBuilder ids = JSONHelper.array(); - b.getNodes().forEach((bb) -> ids.add(bb.getId())); - block.add(NODES, ids); - JSONHelper.JSONArrayBuilder edges = JSONHelper.array(); - b.getSuccessors().forEach((ss) -> edges.add(ss.getName())); - block.add(EDGES, edges); - return block; - } - - private static String createFileName(String graphType, String graphName, int part) { - String p = (part == -1) ? "" : ("." + part); - String gt = graphType.replaceAll("[^\\p{Alnum}]", "") + '_'; - String gn = graphName.replaceAll("[^\\p{Alnum}]", "_"); - return gt + gn + p + ".json"; - } - - private static JSONHelper.JSONObjectBuilder stacktrace(LocationStackFrame lsf) { - HashMap> langStack = new HashMap<>(); - HashSet memo = new HashSet<>(); - - for (LocationStackFrame t = lsf; t != null; t = t.getParent()) { - for (LocationStratum s : t.getStrata()) { - if (!memo.contains(s)) { - memo.add(s); - String lang = s.language; - String methodName = t.getFullMethodName(); - boolean isJava = lang.contentEquals(JAVA); - ArrayList stack = langStack.getOrDefault(lang, new ArrayList<>()); - methodName = (isJava && methodName != null) ? ("(" + methodName + ") ") : ""; - stack.add(methodName + stratumToString(s)); - langStack.put(lang, stack); - } - } - } - JSONHelper.JSONObjectBuilder st = JSONHelper.object(); - for (String s : langStack.keySet()) { - JSONHelper.JSONArrayBuilder stack = JSONHelper.array(); - langStack.get(s).forEach(stack::add); - st.add(s, stack); - } - - return st; - } - - private static String stratumToString(LocationStratum s) { - String str = (s.uri != null ? s.uri : s.file) + ":" + s.line; - // bci needed ? - return str + stratumPosToString(s); - } - - private static String stratumPosToString(LocationStratum s) { - if (s.startOffset > -1 && s.endOffset > -1) { - return "(" + s.startOffset + "-" + s.endOffset + ")"; - } - if (s.startOffset > -1) { - return "(" + s.startOffset + "-?)"; - } - if (s.endOffset > -1) { - return "(?-" + s.endOffset + ")"; - } - return ""; - } -} - -// modified version of com.oracle.truffle.api.utilities.JSONHelper - -final class JSONHelper { - - private JSONHelper() { - } - - private static String quote(CharSequence value) { - StringBuilder builder = new StringBuilder(value.length() + 2); - builder.append('"'); - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - switch (c) { - case '"': - builder.append("\\\""); - break; - case '\\': - builder.append("\\\\"); - break; - case '\b': - builder.append("\\b"); - break; - case '\f': - builder.append("\\f"); - break; - case '\n': - builder.append("\\n"); - break; - case '\r': - builder.append("\\r"); - break; - case '\t': - builder.append("\\t"); - break; - default: { - if (c < ' ') { - builder.append("\\u00"); - builder.append(Character.forDigit((c >> 4) & 0xF, 16)); - builder.append(Character.forDigit(c & 0xF, 16)); - } else { - builder.append(c); - } - } - } - } - builder.append('"'); - return builder.toString(); - } - - public static JSONObjectBuilder object() { - return new JSONObjectBuilder(); - } - - public static JSONReadyObject readyObject(int startSize) { - return new JSONReadyObject(startSize); - } - - public static JSONArrayBuilder array() { - return new JSONArrayBuilder(); - } - - public static JSONReadyArray readyArray(int startSize) { - return new JSONReadyArray(startSize); - } - - public abstract static class JSONStringBuilder { - - private JSONStringBuilder() { - } - - @Override - public final String toString() { - StringBuilder sb = new StringBuilder(); - appendTo(sb); - return sb.toString(); - } - - protected abstract void appendTo(StringBuilder sb); - - protected static void appendValue(StringBuilder sb, Object value) { - if (value instanceof JSONStringBuilder) { - ((JSONStringBuilder) value).appendTo(sb); - } else if (value instanceof Integer || value instanceof Boolean || value == null) { - sb.append(value); - } else { - sb.append(quote(String.valueOf(value))); - } - } - } - - public static final class JSONObjectBuilder extends JSONStringBuilder { - private final Map contents = new LinkedHashMap<>(); - - private JSONObjectBuilder() { - } - - public JSONObjectBuilder add(String key, String value) { - contents.put(key, value); - return this; - } - - public JSONObjectBuilder add(String key, Number value) { - contents.put(key, value); - return this; - } - - public JSONObjectBuilder add(String key, JSONStringBuilder value) { - contents.put(key, value); - return this; - } - - @Override - protected void appendTo(StringBuilder sb) { - sb.append("{"); - boolean comma = false; - for (Map.Entry entry : contents.entrySet()) { - if (comma) { - sb.append(", "); - } - sb.append(quote(entry.getKey())); - sb.append(": "); - appendValue(sb, entry.getValue()); - comma = true; - } - sb.append("}"); - } - } - - public static final class JSONReadyObject extends JSONStringBuilder { - private final Map contents = new LinkedHashMap<>(); - private int size; - - private JSONReadyObject(int startSize) { - size = startSize + 2 /* '{' '}' */; - } - - public JSONReadyObject add(String key, String value) { - put(key, quote(value)); - return this; - } - - public JSONReadyObject add(String key, Number value) { - put(key, value.toString()); - return this; - } - - public JSONReadyObject add(String key, Boolean value) { - put(key, value.toString()); - return this; - } - - public JSONReadyObject add(String key, JSONStringBuilder value) { - put(key, value.toString()); - return this; - } - - private void put(String key, String v) { - size += key.length() + v.length() + 4 /* ", " ": " */; - contents.put(key, v); - } - - public int getSize() { - return size; - } - - @Override - protected void appendTo(StringBuilder sb) { - sb.append("{"); - boolean comma = false; - for (Map.Entry entry : contents.entrySet()) { - if (comma) { - sb.append(", "); - } - sb.append(quote(entry.getKey())); - sb.append(": "); - sb.append(entry.getValue()); - comma = true; - } - sb.append("}"); - } - } - - public static final class JSONArrayBuilder extends JSONStringBuilder { - private final List contents = new ArrayList<>(); - - private JSONArrayBuilder() { - } - - public JSONArrayBuilder add(String value) { - contents.add(value); - return this; - } - - public JSONArrayBuilder add(Number value) { - contents.add(value); - return this; - } - - public JSONArrayBuilder add(Boolean value) { - contents.add(value); - return this; - } - - public JSONArrayBuilder add(JSONStringBuilder value) { - contents.add(value); - return this; - } - - @Override - protected void appendTo(StringBuilder sb) { - sb.append("["); - boolean comma = false; - for (Object value : contents) { - if (comma) { - sb.append(", "); - } - appendValue(sb, value); - comma = true; - } - sb.append("]"); - } - } - - public static final class JSONReadyArray extends JSONStringBuilder { - private final List contents = new ArrayList<>(); - private int size; - - private JSONReadyArray(int startSize) { - size = startSize + 2 /* '[' ']' */; - } - - public JSONReadyArray add(String value) { - addElement(quote(value)); - return this; - } - - public JSONReadyArray add(Number value) { - addElement(value.toString()); - return this; - } - - public JSONReadyArray add(Boolean value) { - addElement(value.toString()); - return this; - } - - public JSONReadyArray add(JSONStringBuilder value) { - addElement(value.toString()); - return this; - } - - private void addElement(String value) { - size += value.length() + 2 /* ", " */; - contents.add(value); - } - - public int getSize() { - return size; - } - - @Override - protected void appendTo(StringBuilder sb) { - sb.append("["); - boolean comma = false; - for (Object value : contents) { - if (comma) { - sb.append(", "); - } - sb.append(value); - comma = true; - } - sb.append("]"); - } - } - -} diff --git a/visualizer/IdealGraphVisualizer/JSONExporter/src/main/nbm/manifest.mf b/visualizer/IdealGraphVisualizer/JSONExporter/src/main/nbm/manifest.mf deleted file mode 100644 index 3bfbca7a1a56..000000000000 --- a/visualizer/IdealGraphVisualizer/JSONExporter/src/main/nbm/manifest.mf +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -OpenIDE-Module-Localizing-Bundle: org/graalvm/visualizer/jsonexporter/Bundle.properties -OpenIDE-Module-Hide-Classpath-Packages: jdk.graal.compiler.graphio.** diff --git a/visualizer/IdealGraphVisualizer/JSONExporter/src/main/resources/org/graalvm/visualizer/jsonexporter/Bundle.properties b/visualizer/IdealGraphVisualizer/JSONExporter/src/main/resources/org/graalvm/visualizer/jsonexporter/Bundle.properties deleted file mode 100644 index adee06c2e90e..000000000000 --- a/visualizer/IdealGraphVisualizer/JSONExporter/src/main/resources/org/graalvm/visualizer/jsonexporter/Bundle.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Localized module labels. Defaults taken from POM (, , ) if unset. -#OpenIDE-Module-Name= -#OpenIDE-Module-Short-Description= -#OpenIDE-Module-Long-Description= -#OpenIDE-Module-Display-Category= -#Fri Mar 22 12:20:09 PDT 2024 diff --git a/visualizer/IdealGraphVisualizer/pom.xml b/visualizer/IdealGraphVisualizer/pom.xml index ad7ced889976..f3f03458dda2 100644 --- a/visualizer/IdealGraphVisualizer/pom.xml +++ b/visualizer/IdealGraphVisualizer/pom.xml @@ -112,7 +112,6 @@ Graal Graph GraphSearch - JSONExporter JavaSources NetworkConnection SelectionCoordinator From 168bb225d623bda17c9549cde7c4f62d4c6b57c5 Mon Sep 17 00:00:00 2001 From: Tom Rodriguez Date: Tue, 21 Oct 2025 08:28:59 -0700 Subject: [PATCH 02/11] Import latest sources and convert to maven --- visualizer/C1Visualizer.md | 32 + visualizer/C1Visualizer/BlockView/pom.xml | 97 ++ .../block/view/BlockTableModel.java | 132 ++ .../block/view/BlockViewTopComponent.java | 233 +++ .../block/view/ShowBlockViewAction.java | 44 + .../BlockView/src/main/nbm/manifest.mf | 6 + .../view/BlockViewTopComponentSettings.xml | 8 + .../view/BlockViewTopComponentWstcref.xml | 7 + .../visualizer/block/view/Bundle.properties | 1 + .../at/ssw/visualizer/block/view/layer.xml | 30 + .../C1Visualizer/BytecodeEditor/pom.xml | 128 ++ .../java/at/ssw/visualizer/bc/BCEditor.java | 51 + .../at/ssw/visualizer/bc/BCEditorKit.java | 52 + .../at/ssw/visualizer/bc/BCEditorSupport.java | 107 ++ .../bc/action/ShowBCEditorAction.java | 138 ++ .../at/ssw/visualizer/bc/icons/Icons.java | 33 + .../java/at/ssw/visualizer/bc/model/BCExample | 18 + .../at/ssw/visualizer/bc/model/BCScanner.java | 80 + .../visualizer/bc/model/BCTextBuilder.java | 249 +++ .../visualizer/bc/model/BCTokenContext.java | 69 + .../BytecodeEditor/src/main/nbm/manifest.mf | 6 + .../at/ssw/visualizer/bc/Bundle.properties | 12 + .../at/ssw/visualizer/bc/icons/bytecode.gif | Bin 0 -> 132 bytes .../resources/at/ssw/visualizer/bc/layer.xml | 68 + .../bc/model/NetBeans-BC-fontsColors.xml | 16 + visualizer/C1Visualizer/BytecodeModel/pom.xml | 97 ++ .../visualizer/bc/model/BytecodeModel.java | 61 + .../bc/modelimpl/BytecodeModelImpl.java | 224 +++ .../bc/modelimpl/BytecodesImpl.java | 87 ++ .../bc/modelimpl/BytecodesParser.java | 174 +++ .../visualizer/bc/modelimpl/MethodName.java | 140 ++ .../ssw/visualizer/bc/options/BC-classpaths | 0 .../visualizer/bc/options/BCOptionPanel.form | 102 ++ .../visualizer/bc/options/BCOptionPanel.java | 196 +++ .../ssw/visualizer/bc/options/BCOptions.java | 55 + .../bc/options/BCOptionsPanelController.java | 94 ++ .../visualizer/bc/options/icons/Icons.java | 34 + .../BytecodeModel/src/main/nbm/manifest.mf | 6 + .../at.ssw.visualizer.bc.model.BytecodeModel | 1 + .../ssw/visualizer/bc/model/Bundle.properties | 1 + .../at/ssw/visualizer/bc/model/layer.xml | 15 + .../visualizer/bc/options/icons/package.gif | Bin 0 -> 412 bytes visualizer/C1Visualizer/BytecodeView/pom.xml | 117 ++ .../bc/view/BCViewTopComponent.java | 91 ++ .../visualizer/bc/view/ShowBCViewAction.java | 44 + .../BytecodeView/src/main/nbm/manifest.mf | 6 + .../bc/view/BCViewTopComponentSettings.xml | 8 + .../bc/view/BCViewTopComponentWstcref.xml | 7 + .../ssw/visualizer/bc/view/Bundle.properties | 1 + .../at/ssw/visualizer/bc/view/layer.xml | 29 + .../C1Visualizer/CompilationModel/pom.xml | 99 ++ .../at/ssw/visualizer/model/Compilation.java | 37 + .../visualizer/model/CompilationElement.java | 41 + .../visualizer/model/CompilationModel.java | 44 + .../at/ssw/visualizer/model/bc/Bytecodes.java | 55 + .../ssw/visualizer/model/cfg/BasicBlock.java | 71 + .../model/cfg/ControlFlowGraph.java | 48 + .../visualizer/model/cfg/IRInstruction.java | 41 + .../at/ssw/visualizer/model/cfg/State.java | 39 + .../ssw/visualizer/model/cfg/StateEntry.java | 41 + .../model/interval/ChildInterval.java | 51 + .../visualizer/model/interval/Interval.java | 41 + .../model/interval/IntervalList.java | 39 + .../ssw/visualizer/model/interval/Range.java | 33 + .../model/interval/UsePosition.java | 33 + .../ssw/visualizer/model/nc/NativeMethod.java | 37 + .../modelimpl/CompilationElementImpl.java | 75 + .../visualizer/modelimpl/CompilationImpl.java | 74 + .../modelimpl/CompilationModelImpl.java | 107 ++ .../modelimpl/bc/BytecodesImpl.java | 188 +++ .../modelimpl/cfg/BasicBlockImpl.java | 171 +++ .../modelimpl/cfg/ControlFlowGraphImpl.java | 104 ++ .../modelimpl/cfg/IRInstructionImpl.java | 96 ++ .../modelimpl/cfg/StateEntryImpl.java | 66 + .../visualizer/modelimpl/cfg/StateImpl.java | 63 + .../modelimpl/interval/ChildIntervalImpl.java | 140 ++ .../modelimpl/interval/IntervalImpl.java | 82 + .../modelimpl/interval/IntervalListImpl.java | 75 + .../modelimpl/interval/RangeImpl.java | 58 + .../modelimpl/interval/UsePositionImpl.java | 58 + .../modelimpl/nc/NativeMethodImpl.java | 50 + .../at/ssw/visualizer/parser/BBHelper.java | 59 + .../at/ssw/visualizer/parser/CFGHelper.java | 175 +++ .../visualizer/parser/CompilationHelper.java | 56 + .../visualizer/parser/CompilationParser.java | 70 + .../ssw/visualizer/parser/CompilerOutput.atg | 357 +++++ .../ssw/visualizer/parser/IntervalHelper.java | 52 + .../visualizer/parser/IntervalListHelper.java | 80 + .../at/ssw/visualizer/parser/Parser.frame | 179 +++ .../java/at/ssw/visualizer/parser/Parser.java | 721 +++++++++ .../at/ssw/visualizer/parser/Scanner.frame | 463 ++++++ .../at/ssw/visualizer/parser/Scanner.java | 544 +++++++ .../CompilationModel/src/main/nbm/manifest.mf | 4 + .../at.ssw.visualizer.model.CompilationModel | 1 + .../at/ssw/visualizer/model/Bundle.properties | 1 + .../C1Visualizer/CompilationView/pom.xml | 132 ++ .../view/CompilationModelNode.java | 226 +++ .../view/CompilationViewTopComponent.java | 207 +++ .../view/ShowCompilationViewAction.java | 44 + .../view/action/OpenCompilationAction.java | 96 ++ .../action/RemoveAllCompilationsAction.java | 50 + .../view/action/RemoveCompilationAction.java | 68 + .../compilation/view/icons/Icons.java | 48 + .../CompilationView/src/main/nbm/manifest.mf | 6 + .../compilation/view/Bundle.properties | 1 + .../CompilationViewTopComponentSettings.xml | 8 + .../CompilationViewTopComponentWstcref.xml | 7 + .../visualizer/compilation/view/icons/cfg.gif | Bin 0 -> 209 bytes .../compilation/view/icons/collapseall.gif | Bin 0 -> 157 bytes .../compilation/view/icons/compilations.gif | Bin 0 -> 145 bytes .../compilation/view/icons/filter.gif | Bin 0 -> 219 bytes .../compilation/view/icons/folder.gif | Bin 0 -> 216 bytes .../compilation/view/icons/intervals.gif | Bin 0 -> 218 bytes .../compilation/view/icons/open.gif | Bin 0 -> 612 bytes .../compilation/view/icons/package.gif | Bin 0 -> 256 bytes .../compilation/view/icons/remove.gif | Bin 0 -> 163 bytes .../compilation/view/icons/removeall.gif | Bin 0 -> 204 bytes .../compilation/view/icons/sort.gif | Bin 0 -> 153 bytes .../ssw/visualizer/compilation/view/layer.xml | 81 + .../C1Visualizer/ControlFlowEditor/pom.xml | 139 ++ .../ssw/visualizer/cfg/CfgEditorContext.java | 35 + .../cfg/action/AbstractCfgEditorAction.java | 116 ++ .../cfg/action/AbstractRouterAction.java | 83 + .../visualizer/cfg/action/ColorAction.java | 248 +++ .../visualizer/cfg/action/ExportAction.java | 142 ++ .../cfg/action/HideEdgesAction.java | 74 + .../HierarchicalCompoundLayoutAction.java | 57 + .../action/HierarchicalNodeLayoutAction.java | 57 + .../visualizer/cfg/action/ShowAllAction.java | 62 + .../cfg/action/ShowCFGEditorAction.java | 84 ++ .../cfg/action/ShowEdgesAction.java | 74 + .../cfg/action/SwitchLoopClustersAction.java | 88 ++ .../cfg/action/UseBezierRouterAction.java | 51 + .../cfg/action/UseDirectLineRouterAction.java | 50 + .../visualizer/cfg/action/ZoominAction.java | 55 + .../visualizer/cfg/action/ZoomoutAction.java | 55 + .../cfg/editor/CfgEditorSupport.java | 99 ++ .../cfg/editor/CfgEditorTopComponent.java | 282 ++++ .../cfg/graph/CfgEventListener.java | 35 + .../at/ssw/visualizer/cfg/graph/CfgScene.java | 856 +++++++++++ .../cfg/graph/EdgeSwitchWidget.java | 313 ++++ .../ssw/visualizer/cfg/graph/EdgeWidget.java | 119 ++ .../cfg/graph/LoopClusterWidget.java | 129 ++ .../ssw/visualizer/cfg/graph/NodeWidget.java | 177 +++ .../visualizer/cfg/graph/SelectionWidget.java | 61 + .../visualizer/cfg/graph/SymmetricAnchor.java | 179 +++ .../layout/HierarchicalCompoundLayout.java | 124 ++ .../graph/layout/HierarchicalNodeLayout.java | 91 ++ .../at/ssw/visualizer/cfg/icons/Icons.java | 40 + .../at/ssw/visualizer/cfg/model/CfgEdge.java | 32 + .../ssw/visualizer/cfg/model/CfgEdgeImpl.java | 72 + .../at/ssw/visualizer/cfg/model/CfgEnv.java | 319 ++++ .../at/ssw/visualizer/cfg/model/CfgNode.java | 45 + .../ssw/visualizer/cfg/model/CfgNodeImpl.java | 155 ++ .../at/ssw/visualizer/cfg/model/LoopInfo.java | 89 ++ .../cfg/preferences/CFGOptionsCategory.java | 54 + .../cfg/preferences/CFGOptionsPanel.java | 504 +++++++ .../CFGOptionsPanelController.java | 85 ++ .../cfg/preferences/CfgPreferences.java | 270 ++++ .../preferences/CfgPreferencesDefaults.java | 45 + .../cfg/preferences/ColorChooserButton.java | 116 ++ .../cfg/preferences/FlagsEditorPanel.java | 269 ++++ .../cfg/preferences/FlagsSetting.java | 107 ++ .../cfg/preferences/FontChooserDialog.java | 59 + .../visualizer/cfg/visual/BezierWidget.java | 367 +++++ .../visualizer/cfg/visual/PolylineRouter.java | 382 +++++ .../cfg/visual/PolylineRouterV2.java | 415 +++++ .../cfg/visual/SplineConnectionWidget.java | 563 +++++++ .../cfg/visual/WidgetCollisionCollector.java | 34 + .../src/main/nbm/manifest.mf | 6 + .../at/ssw/visualizer/cfg/Bundle.properties | 1 + .../ssw/visualizer/cfg/icons/arrangebfs.gif | Bin 0 -> 338 bytes .../ssw/visualizer/cfg/icons/arrangehier.gif | Bin 0 -> 341 bytes .../ssw/visualizer/cfg/icons/arrangeloop.gif | Bin 0 -> 337 bytes .../at/ssw/visualizer/cfg/icons/autosize.gif | Bin 0 -> 186 bytes .../cfg/icons/autosize_selection.gif | Bin 0 -> 194 bytes .../ssw/visualizer/cfg/icons/bezierrouter.gif | Bin 0 -> 193 bytes .../at/ssw/visualizer/cfg/icons/cfg.gif | Bin 0 -> 209 bytes .../at/ssw/visualizer/cfg/icons/cfg32.gif | Bin 0 -> 577 bytes .../at/ssw/visualizer/cfg/icons/cluster.gif | Bin 0 -> 171 bytes .../at/ssw/visualizer/cfg/icons/color.gif | Bin 0 -> 959 bytes .../at/ssw/visualizer/cfg/icons/combine.gif | Bin 0 -> 200 bytes .../visualizer/cfg/icons/combine_disabled.gif | Bin 0 -> 135 bytes .../at/ssw/visualizer/cfg/icons/disk.gif | Bin 0 -> 1057 bytes .../at/ssw/visualizer/cfg/icons/fanrouter.gif | Bin 0 -> 224 bytes .../at/ssw/visualizer/cfg/icons/hideedges.gif | Bin 0 -> 146 bytes .../cfg/icons/hideedges_disabled.gif | Bin 0 -> 146 bytes .../visualizer/cfg/icons/manhattanrouter.gif | Bin 0 -> 214 bytes .../at/ssw/visualizer/cfg/icons/showedges.gif | Bin 0 -> 153 bytes .../cfg/icons/showedges_disabled.gif | Bin 0 -> 153 bytes .../at/ssw/visualizer/cfg/icons/split.gif | Bin 0 -> 204 bytes .../visualizer/cfg/icons/split_disabled.gif | Bin 0 -> 136 bytes .../at/ssw/visualizer/cfg/icons/zoomin.gif | Bin 0 -> 923 bytes .../at/ssw/visualizer/cfg/icons/zoomout.gif | Bin 0 -> 924 bytes .../resources/at/ssw/visualizer/cfg/layer.xml | 69 + .../C1Visualizer/DataFlowEditor/pom.xml | 137 ++ .../action/ShowDataFlowEditorAction.java | 97 ++ .../dataflow/editor/DFEditorSupport.java | 104 ++ .../editor/DataFlowEditorTopComponent.java | 990 ++++++++++++ .../DataFlowEditor/src/main/nbm/manifest.mf | 6 + .../ssw/visualizer/dataflow/Bundle.properties | 1 + .../ssw/visualizer/dataflow/icons/animate.gif | Bin 0 -> 310 bytes .../dataflow/icons/arrangeforce.gif | Bin 0 -> 322 bytes .../dataflow/icons/arrangeforcecluster.gif | Bin 0 -> 332 bytes .../visualizer/dataflow/icons/arrangehier.gif | Bin 0 -> 320 bytes .../dataflow/icons/arrangehieradvanced.gif | Bin 0 -> 320 bytes .../dataflow/icons/arrangehiercluster.gif | Bin 0 -> 332 bytes .../visualizer/dataflow/icons/autolayout.gif | Bin 0 -> 609 bytes .../ssw/visualizer/dataflow/icons/cluster.gif | Bin 0 -> 171 bytes .../visualizer/dataflow/icons/clusterhigh.gif | Bin 0 -> 216 bytes .../visualizer/dataflow/icons/currentnode.gif | Bin 0 -> 565 bytes .../at/ssw/visualizer/dataflow/icons/dfg.gif | Bin 0 -> 209 bytes .../dataflow/icons/directedforce.gif | Bin 0 -> 345 bytes .../at/ssw/visualizer/dataflow/icons/disk.gif | Bin 0 -> 1057 bytes .../ssw/visualizer/dataflow/icons/expall.gif | Bin 0 -> 586 bytes .../visualizer/dataflow/icons/expandblock.gif | Bin 0 -> 222 bytes .../visualizer/dataflow/icons/expblocks.gif | Bin 0 -> 588 bytes .../visualizer/dataflow/icons/fanrouter.gif | Bin 0 -> 224 bytes .../ssw/visualizer/dataflow/icons/hideall.gif | Bin 0 -> 161 bytes .../visualizer/dataflow/icons/hideconst.gif | Bin 0 -> 216 bytes .../dataflow/icons/hideoperation.gif | Bin 0 -> 221 bytes .../visualizer/dataflow/icons/hideparam.gif | Bin 0 -> 217 bytes .../ssw/visualizer/dataflow/icons/hidephi.gif | Bin 0 -> 217 bytes .../ssw/visualizer/dataflow/icons/layout.gif | Bin 0 -> 628 bytes .../dataflow/icons/layoutinvisible.gif | Bin 0 -> 322 bytes .../visualizer/dataflow/icons/linkgrayed.gif | Bin 0 -> 195 bytes .../ssw/visualizer/dataflow/icons/options.gif | Bin 0 -> 564 bytes .../ssw/visualizer/dataflow/icons/showall.gif | Bin 0 -> 164 bytes .../visualizer/dataflow/icons/showconst.gif | Bin 0 -> 218 bytes .../dataflow/icons/showoperation.gif | Bin 0 -> 224 bytes .../visualizer/dataflow/icons/showparam.gif | Bin 0 -> 219 bytes .../ssw/visualizer/dataflow/icons/showphi.gif | Bin 0 -> 219 bytes .../ssw/visualizer/dataflow/icons/zoomin.gif | Bin 0 -> 923 bytes .../ssw/visualizer/dataflow/icons/zoomout.gif | Bin 0 -> 924 bytes .../at/ssw/visualizer/dataflow/layer.xml | 39 + visualizer/C1Visualizer/DataFlowGraph/pom.xml | 84 ++ .../positionmanager/impl/GraphCluster.java | 75 + .../ssw/positionmanager/impl/GraphLink.java | 73 + .../ssw/positionmanager/impl/GraphPort.java | 79 + .../ssw/positionmanager/impl/GraphVertex.java | 114 ++ .../attributes/ExpandNodeSwitchAttribute.java | 90 ++ .../attributes/ExpandStructureAttribute.java | 77 + .../IAdditionalWidgetAttribute.java | 34 + .../attributes/IExpandNodeAttribute.java | 32 + .../attributes/IInvisibilityAttribute.java | 30 + .../dataflow/attributes/INodeAttribute.java | 49 + .../attributes/IPathHighlightAttribute.java | 33 + .../IPopupContributorAttribute.java | 35 + .../dataflow/attributes/ISwitchAttribute.java | 39 + .../attributes/InvisibleAttribute.java | 38 + .../InvisibleNeighbourAttribute.java | 58 + .../SelfSwitchingExpandAttribute.java | 76 + .../attributes/TBExpandAllAttribute.java | 56 + .../attributes/TBInvisibilityAttribute.java | 48 + .../attributes/TBShowBlockAttribute.java | 56 + .../dataflow/graph/ClusterWidget.java | 145 ++ .../dataflow/graph/DirectLineRouter.java | 83 + .../dataflow/graph/HiddenNodesWidget.java | 101 ++ .../dataflow/graph/InscribeNodeWidget.java | 67 + .../graph/InstructionConnectionWidget.java | 121 ++ .../graph/InstructionNodeGraphScene.java | 1332 +++++++++++++++++ .../dataflow/graph/InstructionNodeWidget.java | 879 +++++++++++ .../graph/InstructionSceneListener.java | 37 + .../dataflow/graph/SetLocationAnimator.java | 131 ++ .../dataflow/instructions/Instruction.java | 203 +++ .../dataflow/instructions/InstructionSet.java | 103 ++ .../instructions/InstructionSetGenerator.java | 190 +++ .../DataFlowGraph/src/main/nbm/manifest.mf | 5 + .../dataflow/graph/Bundle.properties | 1 + visualizer/C1Visualizer/DataFlowView/pom.xml | 97 ++ .../dataflow/view/DataflowTableModel.java | 201 +++ .../view/DataflowViewTopComponent.java | 213 +++ .../dataflow/view/ShowDataflowViewAction.java | 44 + .../DataFlowView/src/main/nbm/manifest.mf | 6 + .../dataflow/view/Bundle.properties | 1 + .../view/DataflowViewTopComponentSettings.xml | 8 + .../view/DataflowViewTopComponentWstcref.xml | 7 + .../at/ssw/visualizer/dataflow/view/layer.xml | 29 + visualizer/C1Visualizer/GraphHelper/pom.xml | 57 + .../at/ssw/visualizer/graphhelper/Block.java | 131 ++ .../ssw/visualizer/graphhelper/DiGraph.java | 803 ++++++++++ .../at/ssw/visualizer/graphhelper/Edge.java | 62 + .../ssw/visualizer/graphhelper/Embedding.java | 297 ++++ .../at/ssw/visualizer/graphhelper/Node.java | 63 + .../GraphHelper/src/main/nbm/manifest.mf | 5 + .../visualizer/graphhelper/Bundle.properties | 1 + .../C1Visualizer/GraphLayoutAPI/pom.xml | 58 + .../java/at/ssw/positionmanager/Cluster.java | 35 + .../at/ssw/positionmanager/LayoutGraph.java | 194 +++ .../at/ssw/positionmanager/LayoutManager.java | 34 + .../java/at/ssw/positionmanager/Link.java | 37 + .../java/at/ssw/positionmanager/Port.java | 36 + .../java/at/ssw/positionmanager/Vertex.java | 44 + .../positionmanager/export/GMLFileExport.java | 144 ++ .../export/LayoutGraphExporter.java | 36 + .../GraphLayoutAPI/src/main/nbm/manifest.mf | 5 + .../at/ssw/positionmanager/Bundle.properties | 1 + .../C1Visualizer/GraphLayoutImpl/pom.xml | 76 + .../layout/CompoundForceLayouter.java | 511 +++++++ .../CompoundHierarchicalNodesLayouter.java | 257 ++++ .../layout/ExternalGraphLayoutWrapper.java | 111 ++ .../layout/ExternalGraphLayouter.java | 46 + .../at/ssw/dataflow/layout/ForceLayouter.java | 478 ++++++ .../layout/HierarchicalNodesLayouter.java | 211 +++ .../layout/MagneticSpringForceLayouter.java | 477 ++++++ .../at/ssw/dataflow/layout/RoutingHelper.java | 179 +++ .../at/ssw/dataflow/layout/doublePoint.java | 43 + .../options/BooleanStringValidator.java | 48 + .../options/DoubleStringValidator.java | 76 + .../dataflow/options/IntStringValidator.java | 75 + .../at/ssw/dataflow/options/OptionEditor.java | 148 ++ .../ssw/dataflow/options/OptionProvider.java | 61 + .../at/ssw/dataflow/options/Validator.java | 42 + .../positioning/BasicLineGenerator.java | 44 + .../positioning/BezierLineGenerator.java | 44 + .../positioning/ClusterEdge.java | 63 + .../positioning/ClusterIngoingConnection.java | 76 + .../positioning/ClusterInputSlotNode.java | 160 ++ .../positioning/ClusterNode.java | 217 +++ .../ClusterOutgoingConnection.java | 68 + .../positioning/ClusterOutputSlotNode.java | 167 +++ .../ssw/graphanalyzer/positioning/Curves.java | 202 +++ .../ssw/graphanalyzer/positioning/Edge.java | 86 ++ .../ssw/graphanalyzer/positioning/Graph.java | 300 ++++ .../HierarchicalClusterLayoutManager.java | 225 +++ .../HierarchicalLayoutManager.java | 1124 ++++++++++++++ .../positioning/InterClusterConnection.java | 74 + .../positioning/LineGenerator.java | 38 + .../ssw/graphanalyzer/positioning/Node.java | 158 ++ .../positioning/SplineLineGenerator.java | 48 + .../GraphLayoutImpl/src/main/nbm/manifest.mf | 5 + .../at/ssw/dataflow/Bundle.properties | 1 + .../IntermediateCodeEditor/pom.xml | 123 ++ .../java/at/ssw/visualizer/ir/IREditor.java | 47 + .../at/ssw/visualizer/ir/IREditorKit.java | 79 + .../at/ssw/visualizer/ir/IREditorSupport.java | 65 + .../ir/action/CollapseAllAction.java | 58 + .../visualizer/ir/action/ExpandAllAction.java | 58 + .../visualizer/ir/action/ExpandHIRAction.java | 62 + .../visualizer/ir/action/ExpandLIRAction.java | 61 + .../ir/action/ShowIREditorAction.java | 82 + .../at/ssw/visualizer/ir/icons/Icons.java | 40 + .../java/at/ssw/visualizer/ir/model/IRExample | 13 + .../at/ssw/visualizer/ir/model/IRScanner.java | 78 + .../visualizer/ir/model/IRTextBuilder.java | 507 +++++++ .../visualizer/ir/model/IRTokenContext.java | 63 + .../src/main/nbm/manifest.mf | 6 + .../at/ssw/visualizer/ir/Bundle.properties | 9 + .../ssw/visualizer/ir/icons/collapseall.gif | Bin 0 -> 157 bytes .../at/ssw/visualizer/ir/icons/expandall.gif | Bin 0 -> 164 bytes .../at/ssw/visualizer/ir/icons/expandhir.gif | Bin 0 -> 253 bytes .../at/ssw/visualizer/ir/icons/expandlir.gif | Bin 0 -> 246 bytes .../at/ssw/visualizer/ir/icons/ir.gif | Bin 0 -> 200 bytes .../resources/at/ssw/visualizer/ir/layer.xml | 112 ++ .../ir/model/NetBeans-IR-fontsColors.xml | 13 + .../IntermediateCodeViews/pom.xml | 117 ++ .../ir/view/HIRViewTopComponent.java | 91 ++ .../ir/view/LIRViewTopComponent.java | 90 ++ .../visualizer/ir/view/ShowHIRViewAction.java | 44 + .../visualizer/ir/view/ShowLIRViewAction.java | 44 + .../ir/view/ShowStateViewAction.java | 44 + .../ir/view/StateViewTopComponent.java | 90 ++ .../src/main/nbm/manifest.mf | 6 + .../ssw/visualizer/ir/view/Bundle.properties | 1 + .../ir/view/HIRViewTopComponentSettings.xml | 8 + .../ir/view/HIRViewTopComponentWstcref.xml | 7 + .../ir/view/LIRViewTopComponentSettings.xml | 8 + .../ir/view/LIRViewTopComponentWstcref.xml | 7 + .../ir/view/StateViewTopComponentSettings.xml | 8 + .../ir/view/StateViewTopComponentWstcref.xml | 7 + .../at/ssw/visualizer/ir/view/layer.xml | 49 + .../C1Visualizer/IntervalEditor/pom.xml | 107 ++ .../visualizer/interval/IntervalCanvas.java | 577 +++++++ .../interval/IntervalEditorSupport.java | 104 ++ .../interval/IntervalEditorTopComponent.java | 262 ++++ .../interval/ShowIntervalEditorAction.java | 72 + .../ssw/visualizer/interval/ViewSettings.java | 179 +++ .../ssw/visualizer/interval/icons/Icons.java | 43 + .../IntervalEditor/src/main/nbm/manifest.mf | 6 + .../ssw/visualizer/interval/Bundle.properties | 1 + .../visualizer/interval/icons/hsizelarge.gif | Bin 0 -> 166 bytes .../visualizer/interval/icons/hsizemedium.gif | Bin 0 -> 164 bytes .../visualizer/interval/icons/hsizesmall.gif | Bin 0 -> 136 bytes .../visualizer/interval/icons/intervals.gif | Bin 0 -> 218 bytes .../visualizer/interval/icons/vsizelarge.gif | Bin 0 -> 155 bytes .../visualizer/interval/icons/vsizemedium.gif | Bin 0 -> 150 bytes .../visualizer/interval/icons/vsizesmall.gif | Bin 0 -> 113 bytes .../at/ssw/visualizer/interval/layer.xml | 27 + visualizer/C1Visualizer/IntervalView/pom.xml | 97 ++ .../IntervalView/src/main/nbm/manifest.mf | 4 + .../interval/view/Bundle.properties | 1 + .../C1Visualizer/NativeCodeEditor/pom.xml | 128 ++ .../java/at/ssw/visualizer/nc/NCEditor.java | 45 + .../at/ssw/visualizer/nc/NCEditorKit.java | 85 ++ .../at/ssw/visualizer/nc/NCEditorSupport.java | 61 + .../nc/action/CollapseCommentsAction.java | 59 + .../nc/action/ExpandCommentsAction.java | 59 + .../nc/action/ShowNCEditorAction.java | 86 ++ .../at/ssw/visualizer/nc/icons/Icons.java | 35 + .../nc/model/HexCodeFileSupport.java | 30 + .../java/at/ssw/visualizer/nc/model/NCExample | 9 + .../at/ssw/visualizer/nc/model/NCScanner.java | 157 ++ .../visualizer/nc/model/NCTextBuilder.java | 319 ++++ .../visualizer/nc/model/NCTokenContext.java | 68 + .../com/oracle/max/hcfdis/HexCodeFile.java | 558 +++++++ .../com/oracle/max/hcfdis/HexCodeFileDis.java | 529 +++++++ .../NativeCodeEditor/src/main/nbm/manifest.mf | 6 + .../at/ssw/visualizer/nc/Bundle.properties | 10 + .../ssw/visualizer/nc/icons/collapselir.gif | Bin 0 -> 241 bytes .../at/ssw/visualizer/nc/icons/expandlir.gif | Bin 0 -> 246 bytes .../at/ssw/visualizer/nc/icons/nativecode.gif | Bin 0 -> 91 bytes .../resources/at/ssw/visualizer/nc/layer.xml | 83 + .../nc/model/NetBeans-NC-fontsColors.xml | 19 + .../C1Visualizer/NativeCodeView/pom.xml | 102 ++ .../ssw/visualizer/nc/view/NCViewAction.java | 45 + .../nc/view/NCViewTopComponent.java | 104 ++ .../NativeCodeView/src/main/nbm/manifest.mf | 6 + .../ssw/visualizer/nc/view/Bundle.properties | 4 + .../nc/view/NCViewTopComponentSettings.xml | 12 + .../nc/view/NCViewTopComponentWstcref.xml | 11 + .../at/ssw/visualizer/nc/view/layer.xml | 29 + visualizer/C1Visualizer/TextEditor/pom.xml | 139 ++ .../at/ssw/visualizer/texteditor/Editor.java | 149 ++ .../ssw/visualizer/texteditor/EditorKit.java | 53 + .../visualizer/texteditor/EditorSupport.java | 173 +++ .../texteditor/fold/FoldManager.java | 103 ++ .../highlight/HighlightsContainer.java | 141 ++ .../hyperlink/HyperlinkProvider.java | 79 + .../texteditor/model/BlockRegion.java | 57 + .../texteditor/model/FoldingRegion.java | 54 + .../texteditor/model/HoverParser.java | 139 ++ .../visualizer/texteditor/model/Scanner.java | 215 +++ .../ssw/visualizer/texteditor/model/Text.java | 113 ++ .../texteditor/model/TextBuilder.java | 135 ++ .../texteditor/model/TextRegion.java | 49 + .../texteditor/tooltip/StyledToolTip.java | 66 + .../texteditor/tooltip/ToolTipAction.java | 89 ++ .../view/AbstractTextViewTopComponent.java | 100 ++ .../view/PaintBugWorkaroundBar.java | 63 + .../TextEditor/src/main/nbm/manifest.mf | 6 + .../visualizer/texteditor/Bundle.properties | 1 + .../at/ssw/visualizer/texteditor/layer.xml | 26 + .../ssw/visualizer/texteditor/preferences.xml | 6 + visualizer/C1Visualizer/VisualizerUI/pom.xml | 73 + .../java/at/ssw/visualizer/DefaultExample | 1 + .../at/ssw/visualizer/core/focus/Focus.java | 52 + .../visualizer/core/selection/Selection.java | 94 ++ .../core/selection/SelectionManager.java | 78 + .../core/selection/SelectionProvider.java | 31 + .../VisualizerUI/src/main/nbm/manifest.mf | 6 + .../at/ssw/visualizer/Bundle.properties | 23 + .../at/ssw/visualizer/NetBeans-editor.xml | 23 + .../ssw/visualizer/NetBeans-fontsColors.xml | 8 + .../at/ssw/visualizer/bottomLeftWsmode.xml | 16 + .../at/ssw/visualizer/bottomRightWsmode.xml | 16 + .../resources/at/ssw/visualizer/layer.xml | 188 +++ .../at/ssw/visualizer/leftWsmode.xml | 18 + visualizer/C1Visualizer/application/pom.xml | 1202 +++++++++++++++ .../src/main/resources/c1visualizer.clusters | 2 + .../src/main/resources/c1visualizer.conf | 37 + visualizer/C1Visualizer/branding.jnlp | 15 + visualizer/C1Visualizer/branding/pom.xml | 97 ++ .../netbeans/core/startup/Bundle.properties | 8 + .../org/netbeans/core/startup/frame.gif | Bin 0 -> 667 bytes .../org/netbeans/core/startup/frame48.gif | Bin 0 -> 2788 bytes .../org/netbeans/core/startup/splash.gif | Bin 0 -> 127636 bytes .../core/windows/view/ui/Bundle.properties | 4 + visualizer/C1Visualizer/master.jnlp | 25 + visualizer/C1Visualizer/pom.xml | 150 ++ visualizer/IdealGraphVisualizer/pom.xml | 2 +- 470 files changed, 42029 insertions(+), 1 deletion(-) create mode 100644 visualizer/C1Visualizer.md create mode 100644 visualizer/C1Visualizer/BlockView/pom.xml create mode 100644 visualizer/C1Visualizer/BlockView/src/main/java/at/ssw/visualizer/block/view/BlockTableModel.java create mode 100644 visualizer/C1Visualizer/BlockView/src/main/java/at/ssw/visualizer/block/view/BlockViewTopComponent.java create mode 100644 visualizer/C1Visualizer/BlockView/src/main/java/at/ssw/visualizer/block/view/ShowBlockViewAction.java create mode 100644 visualizer/C1Visualizer/BlockView/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/BlockViewTopComponentSettings.xml create mode 100644 visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/BlockViewTopComponentWstcref.xml create mode 100644 visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/Bundle.properties create mode 100644 visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/layer.xml create mode 100644 visualizer/C1Visualizer/BytecodeEditor/pom.xml create mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/BCEditor.java create mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/BCEditorKit.java create mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/BCEditorSupport.java create mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/action/ShowBCEditorAction.java create mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/icons/Icons.java create mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCExample create mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCScanner.java create mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCTextBuilder.java create mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCTokenContext.java create mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/Bundle.properties create mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/icons/bytecode.gif create mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/layer.xml create mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/model/NetBeans-BC-fontsColors.xml create mode 100644 visualizer/C1Visualizer/BytecodeModel/pom.xml create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/model/BytecodeModel.java create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/BytecodeModelImpl.java create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/BytecodesImpl.java create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/BytecodesParser.java create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/MethodName.java create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BC-classpaths create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptionPanel.form create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptionPanel.java create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptions.java create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptionsPanelController.java create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/icons/Icons.java create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/resources/META-INF/services/at.ssw.visualizer.bc.model.BytecodeModel create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/model/Bundle.properties create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/model/layer.xml create mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/options/icons/package.gif create mode 100644 visualizer/C1Visualizer/BytecodeView/pom.xml create mode 100644 visualizer/C1Visualizer/BytecodeView/src/main/java/at/ssw/visualizer/bc/view/BCViewTopComponent.java create mode 100644 visualizer/C1Visualizer/BytecodeView/src/main/java/at/ssw/visualizer/bc/view/ShowBCViewAction.java create mode 100644 visualizer/C1Visualizer/BytecodeView/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/BCViewTopComponentSettings.xml create mode 100644 visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/BCViewTopComponentWstcref.xml create mode 100644 visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/Bundle.properties create mode 100644 visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/layer.xml create mode 100644 visualizer/C1Visualizer/CompilationModel/pom.xml create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/Compilation.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/CompilationElement.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/CompilationModel.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/bc/Bytecodes.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/BasicBlock.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/ControlFlowGraph.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/IRInstruction.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/State.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/StateEntry.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/ChildInterval.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/Interval.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/IntervalList.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/Range.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/UsePosition.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/nc/NativeMethod.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/CompilationElementImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/CompilationImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/CompilationModelImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/bc/BytecodesImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/BasicBlockImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/ControlFlowGraphImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/IRInstructionImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/StateEntryImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/StateImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/ChildIntervalImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/IntervalImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/IntervalListImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/RangeImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/UsePositionImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/nc/NativeMethodImpl.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/BBHelper.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CFGHelper.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CompilationHelper.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CompilationParser.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CompilerOutput.atg create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/IntervalHelper.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/IntervalListHelper.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Parser.frame create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Parser.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Scanner.frame create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Scanner.java create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/resources/META-INF/services/at.ssw.visualizer.model.CompilationModel create mode 100644 visualizer/C1Visualizer/CompilationModel/src/main/resources/at/ssw/visualizer/model/Bundle.properties create mode 100644 visualizer/C1Visualizer/CompilationView/pom.xml create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/CompilationModelNode.java create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/CompilationViewTopComponent.java create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/ShowCompilationViewAction.java create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/OpenCompilationAction.java create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/RemoveAllCompilationsAction.java create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/RemoveCompilationAction.java create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/icons/Icons.java create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/Bundle.properties create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/CompilationViewTopComponentSettings.xml create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/CompilationViewTopComponentWstcref.xml create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/cfg.gif create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/collapseall.gif create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/compilations.gif create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/filter.gif create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/folder.gif create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/intervals.gif create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/open.gif create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/package.gif create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/remove.gif create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/removeall.gif create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/sort.gif create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/layer.xml create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/pom.xml create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/CfgEditorContext.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/AbstractCfgEditorAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/AbstractRouterAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ColorAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ExportAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/HideEdgesAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/HierarchicalCompoundLayoutAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/HierarchicalNodeLayoutAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ShowAllAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ShowCFGEditorAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ShowEdgesAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/SwitchLoopClustersAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/UseBezierRouterAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/UseDirectLineRouterAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ZoominAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ZoomoutAction.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/editor/CfgEditorSupport.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/editor/CfgEditorTopComponent.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/CfgEventListener.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/CfgScene.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/EdgeSwitchWidget.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/EdgeWidget.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/LoopClusterWidget.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/NodeWidget.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/SelectionWidget.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/SymmetricAnchor.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/layout/HierarchicalCompoundLayout.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/layout/HierarchicalNodeLayout.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/icons/Icons.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgEdge.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgEdgeImpl.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgEnv.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgNode.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgNodeImpl.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/LoopInfo.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CFGOptionsCategory.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CFGOptionsPanel.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CFGOptionsPanelController.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CfgPreferences.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CfgPreferencesDefaults.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/ColorChooserButton.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/FlagsEditorPanel.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/FlagsSetting.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/FontChooserDialog.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/BezierWidget.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/PolylineRouter.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/PolylineRouterV2.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/SplineConnectionWidget.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/WidgetCollisionCollector.java create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/Bundle.properties create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/arrangebfs.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/arrangehier.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/arrangeloop.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/autosize.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/autosize_selection.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/bezierrouter.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/cfg.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/cfg32.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/cluster.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/color.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/combine.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/combine_disabled.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/disk.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/fanrouter.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/hideedges.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/hideedges_disabled.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/manhattanrouter.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/showedges.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/showedges_disabled.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/split.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/split_disabled.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/zoomin.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/zoomout.gif create mode 100644 visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/layer.xml create mode 100644 visualizer/C1Visualizer/DataFlowEditor/pom.xml create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/java/at/ssw/visualizer/dataflow/action/ShowDataFlowEditorAction.java create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/java/at/ssw/visualizer/dataflow/editor/DFEditorSupport.java create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/java/at/ssw/visualizer/dataflow/editor/DataFlowEditorTopComponent.java create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/Bundle.properties create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/animate.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/arrangeforce.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/arrangeforcecluster.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/arrangehier.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/arrangehieradvanced.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/arrangehiercluster.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/autolayout.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/cluster.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/clusterhigh.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/currentnode.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/dfg.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/directedforce.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/disk.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/expall.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/expandblock.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/expblocks.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/fanrouter.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hideall.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hideconst.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hideoperation.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hideparam.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hidephi.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/layout.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/layoutinvisible.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/linkgrayed.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/options.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/showall.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/showconst.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/showoperation.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/showparam.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/showphi.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/zoomin.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/zoomout.gif create mode 100644 visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/layer.xml create mode 100644 visualizer/C1Visualizer/DataFlowGraph/pom.xml create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphCluster.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphLink.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphPort.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphVertex.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/ExpandNodeSwitchAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/ExpandStructureAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IAdditionalWidgetAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IExpandNodeAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IInvisibilityAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/INodeAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IPathHighlightAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IPopupContributorAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/ISwitchAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/InvisibleAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/InvisibleNeighbourAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/SelfSwitchingExpandAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/TBExpandAllAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/TBInvisibilityAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/TBShowBlockAttribute.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/ClusterWidget.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/DirectLineRouter.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/HiddenNodesWidget.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InscribeNodeWidget.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionConnectionWidget.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionNodeGraphScene.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionNodeWidget.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionSceneListener.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/SetLocationAnimator.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/instructions/Instruction.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/instructions/InstructionSet.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/instructions/InstructionSetGenerator.java create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/DataFlowGraph/src/main/resources/at/ssw/visualizer/dataflow/graph/Bundle.properties create mode 100644 visualizer/C1Visualizer/DataFlowView/pom.xml create mode 100644 visualizer/C1Visualizer/DataFlowView/src/main/java/at/ssw/visualizer/dataflow/view/DataflowTableModel.java create mode 100644 visualizer/C1Visualizer/DataFlowView/src/main/java/at/ssw/visualizer/dataflow/view/DataflowViewTopComponent.java create mode 100644 visualizer/C1Visualizer/DataFlowView/src/main/java/at/ssw/visualizer/dataflow/view/ShowDataflowViewAction.java create mode 100644 visualizer/C1Visualizer/DataFlowView/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/Bundle.properties create mode 100644 visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/DataflowViewTopComponentSettings.xml create mode 100644 visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/DataflowViewTopComponentWstcref.xml create mode 100644 visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/layer.xml create mode 100644 visualizer/C1Visualizer/GraphHelper/pom.xml create mode 100644 visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Block.java create mode 100644 visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/DiGraph.java create mode 100644 visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Edge.java create mode 100644 visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Embedding.java create mode 100644 visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Node.java create mode 100644 visualizer/C1Visualizer/GraphHelper/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/GraphHelper/src/main/resources/at/ssw/visualizer/graphhelper/Bundle.properties create mode 100644 visualizer/C1Visualizer/GraphLayoutAPI/pom.xml create mode 100644 visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Cluster.java create mode 100644 visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/LayoutGraph.java create mode 100644 visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/LayoutManager.java create mode 100644 visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Link.java create mode 100644 visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Port.java create mode 100644 visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Vertex.java create mode 100644 visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/export/GMLFileExport.java create mode 100644 visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/export/LayoutGraphExporter.java create mode 100644 visualizer/C1Visualizer/GraphLayoutAPI/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/GraphLayoutAPI/src/main/resources/at/ssw/positionmanager/Bundle.properties create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/pom.xml create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/CompoundForceLayouter.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/CompoundHierarchicalNodesLayouter.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/ExternalGraphLayoutWrapper.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/ExternalGraphLayouter.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/ForceLayouter.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/HierarchicalNodesLayouter.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/MagneticSpringForceLayouter.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/RoutingHelper.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/doublePoint.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/BooleanStringValidator.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/DoubleStringValidator.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/IntStringValidator.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/OptionEditor.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/OptionProvider.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/Validator.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/BasicLineGenerator.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/BezierLineGenerator.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterEdge.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterIngoingConnection.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterInputSlotNode.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterNode.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterOutgoingConnection.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterOutputSlotNode.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Curves.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Edge.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Graph.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/HierarchicalClusterLayoutManager.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/HierarchicalLayoutManager.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/InterClusterConnection.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/LineGenerator.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Node.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/SplineLineGenerator.java create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/GraphLayoutImpl/src/main/resources/at/ssw/dataflow/Bundle.properties create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/pom.xml create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/IREditor.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/IREditorKit.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/IREditorSupport.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/CollapseAllAction.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ExpandAllAction.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ExpandHIRAction.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ExpandLIRAction.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ShowIREditorAction.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/icons/Icons.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRExample create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRScanner.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRTextBuilder.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRTokenContext.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/Bundle.properties create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/icons/collapseall.gif create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/icons/expandall.gif create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/icons/expandhir.gif create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/icons/expandlir.gif create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/icons/ir.gif create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/layer.xml create mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/model/NetBeans-IR-fontsColors.xml create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/pom.xml create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/HIRViewTopComponent.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/LIRViewTopComponent.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/ShowHIRViewAction.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/ShowLIRViewAction.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/ShowStateViewAction.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/StateViewTopComponent.java create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/Bundle.properties create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/HIRViewTopComponentSettings.xml create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/HIRViewTopComponentWstcref.xml create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/LIRViewTopComponentSettings.xml create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/LIRViewTopComponentWstcref.xml create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/StateViewTopComponentSettings.xml create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/StateViewTopComponentWstcref.xml create mode 100644 visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/layer.xml create mode 100644 visualizer/C1Visualizer/IntervalEditor/pom.xml create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/IntervalCanvas.java create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/IntervalEditorSupport.java create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/IntervalEditorTopComponent.java create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/ShowIntervalEditorAction.java create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/ViewSettings.java create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/icons/Icons.java create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/Bundle.properties create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/icons/hsizelarge.gif create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/icons/hsizemedium.gif create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/icons/hsizesmall.gif create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/icons/intervals.gif create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/icons/vsizelarge.gif create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/icons/vsizemedium.gif create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/icons/vsizesmall.gif create mode 100644 visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/layer.xml create mode 100644 visualizer/C1Visualizer/IntervalView/pom.xml create mode 100644 visualizer/C1Visualizer/IntervalView/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/IntervalView/src/main/resources/at/ssw/visualizer/interval/view/Bundle.properties create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/pom.xml create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/NCEditor.java create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/NCEditorKit.java create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/NCEditorSupport.java create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/action/CollapseCommentsAction.java create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/action/ExpandCommentsAction.java create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/action/ShowNCEditorAction.java create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/icons/Icons.java create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/HexCodeFileSupport.java create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCExample create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCScanner.java create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTextBuilder.java create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTokenContext.java create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/com/oracle/max/hcfdis/HexCodeFile.java create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/com/oracle/max/hcfdis/HexCodeFileDis.java create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/Bundle.properties create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/icons/collapselir.gif create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/icons/expandlir.gif create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/icons/nativecode.gif create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/layer.xml create mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/model/NetBeans-NC-fontsColors.xml create mode 100644 visualizer/C1Visualizer/NativeCodeView/pom.xml create mode 100644 visualizer/C1Visualizer/NativeCodeView/src/main/java/at/ssw/visualizer/nc/view/NCViewAction.java create mode 100644 visualizer/C1Visualizer/NativeCodeView/src/main/java/at/ssw/visualizer/nc/view/NCViewTopComponent.java create mode 100644 visualizer/C1Visualizer/NativeCodeView/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/Bundle.properties create mode 100644 visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/NCViewTopComponentSettings.xml create mode 100644 visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/NCViewTopComponentWstcref.xml create mode 100644 visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/layer.xml create mode 100644 visualizer/C1Visualizer/TextEditor/pom.xml create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/Editor.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/EditorKit.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/EditorSupport.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/fold/FoldManager.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/highlight/HighlightsContainer.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/hyperlink/HyperlinkProvider.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/BlockRegion.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/FoldingRegion.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/HoverParser.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/Scanner.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/Text.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/TextBuilder.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/TextRegion.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/tooltip/StyledToolTip.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/tooltip/ToolTipAction.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/view/AbstractTextViewTopComponent.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/view/PaintBugWorkaroundBar.java create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/resources/at/ssw/visualizer/texteditor/Bundle.properties create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/resources/at/ssw/visualizer/texteditor/layer.xml create mode 100644 visualizer/C1Visualizer/TextEditor/src/main/resources/at/ssw/visualizer/texteditor/preferences.xml create mode 100644 visualizer/C1Visualizer/VisualizerUI/pom.xml create mode 100644 visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/DefaultExample create mode 100644 visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/focus/Focus.java create mode 100644 visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/selection/Selection.java create mode 100644 visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/selection/SelectionManager.java create mode 100644 visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/selection/SelectionProvider.java create mode 100644 visualizer/C1Visualizer/VisualizerUI/src/main/nbm/manifest.mf create mode 100644 visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/Bundle.properties create mode 100644 visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/NetBeans-editor.xml create mode 100644 visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/NetBeans-fontsColors.xml create mode 100644 visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/bottomLeftWsmode.xml create mode 100644 visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/bottomRightWsmode.xml create mode 100644 visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/layer.xml create mode 100644 visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/leftWsmode.xml create mode 100644 visualizer/C1Visualizer/application/pom.xml create mode 100644 visualizer/C1Visualizer/application/src/main/resources/c1visualizer.clusters create mode 100644 visualizer/C1Visualizer/application/src/main/resources/c1visualizer.conf create mode 100644 visualizer/C1Visualizer/branding.jnlp create mode 100644 visualizer/C1Visualizer/branding/pom.xml create mode 100644 visualizer/C1Visualizer/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/Bundle.properties create mode 100644 visualizer/C1Visualizer/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/frame.gif create mode 100644 visualizer/C1Visualizer/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/frame48.gif create mode 100644 visualizer/C1Visualizer/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/splash.gif create mode 100644 visualizer/C1Visualizer/branding/src/main/nbm-branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties create mode 100644 visualizer/C1Visualizer/master.jnlp create mode 100644 visualizer/C1Visualizer/pom.xml diff --git a/visualizer/C1Visualizer.md b/visualizer/C1Visualizer.md new file mode 100644 index 000000000000..79b4d22aaa08 --- /dev/null +++ b/visualizer/C1Visualizer.md @@ -0,0 +1,32 @@ +# Readme for C1Visualizer + + +# Developing the C1Visualizer + +C1Visualizer is based on Netbeans 26 but can be often be worked on with later releases or with any +IDE that supports Maven. Care must be taken not to commmit anything that would keep it from working +with 26 so any automatic changes to property file updates by later NetBeans versions shouldn't be +pushed. The packaged product is built by: +``` +cd C1Visualizer +mvn package +``` + +# Regenerating the cfg file parser + +The cfg file parser is based on CoCo/R which hasn't been updated since 2018 and doesn't support +large files. For simplicity, it has been checked in and some bugs with handling of large files were +fixed. It can be regenerated by getting Coco.jar from https://ssw.jku.at/Research/Projects/Coco and +running the following command line. Note that still will overwrite any locals fixes but this should +never be necessary. + +``` +java -jar Coco.jar -o CompilationModel/src/main/java/at/ssw/visualizer/parser \ + -package at.ssw.visualizer.parser CompilationModel/src/main/java/at/ssw/visualizer/parser/CompilerOutput.atg +``` + +# Releasing a new version + +As this is now maven based, `mvn -B release:prepare` is used to update to new versions which will automatically bump the module versions. + +The resulting zip file will be in `C1Visualizer/application/target`. diff --git a/visualizer/C1Visualizer/BlockView/pom.xml b/visualizer/C1Visualizer/BlockView/pom.xml new file mode 100644 index 000000000000..5a380fa39a9b --- /dev/null +++ b/visualizer/C1Visualizer/BlockView/pom.xml @@ -0,0 +1,97 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + BlockView + 1.13-SNAPSHOT + nbm + BlockView + + UTF-8 + + + + at.ssw.visualizer + VisualizerUI + ${project.version} + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + org.netbeans.api + org-openide-explorer + ${netbeans.version} + + + org.netbeans.api + org-openide-loaders + ${netbeans.version} + + + org.netbeans.api + org-openide-nodes + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.BlockView + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/BlockView/src/main/java/at/ssw/visualizer/block/view/BlockTableModel.java b/visualizer/C1Visualizer/BlockView/src/main/java/at/ssw/visualizer/block/view/BlockTableModel.java new file mode 100644 index 000000000000..8dab66ee7a06 --- /dev/null +++ b/visualizer/C1Visualizer/BlockView/src/main/java/at/ssw/visualizer/block/view/BlockTableModel.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.block.view; + +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import java.util.Collections; +import java.util.List; +import javax.swing.table.AbstractTableModel; + +/** + * TableModel containing the blocks of the currently selected view + * + * @author Bernhard Stiftner + * @author Christian Wimmer + */ +public class BlockTableModel extends AbstractTableModel { + + public static final int BLOCK_TABLE_NAME_COL_IDX = 0; + public static final int BLOCK_TABLE_BCI_COL_IDX = 1; + public static final int BLOCK_TABLE_FLAGS_COL_IDX = 2; + public static final int BLOCK_TABLE_LOOP_DEPTH_COL_IDX = 3; + public static final int BLOCK_TABLE_LOOP_INDEX_COL_IDX = 4; + public static final int BLOCK_TABLE_DOMINATOR_COL_IDX = 5; + public static final int BLOCK_TABLE_PREDECESSORS_COL_IDX = 6; + public static final int BLOCK_TABLE_SUCCESSORS_COL_IDX = 7; + public static final int BLOCK_TABLE_XHANDLERS_COL_IDX = 8; + public static final int BLOCK_TABLE_PROBABILITY_COL_IDX = 9; + + public static final String[] COLUMN_NAMES = new String[]{"Name", "BCI", "Flags", "Loop Depth", "Loop Index", "Dominator", "Predecessors", "Successors", "XHandlers", "Probability"}; + public static final int[] COLUMN_WIDTHS = new int[]{60, 60, 60, 80, 80, 60, 120, 120, 120}; + + private List blocks = Collections.emptyList(); + + public void setControlFlowGraph(ControlFlowGraph cfg) { + if (cfg == null) { + blocks = Collections.emptyList(); + } else { + blocks = cfg.getBasicBlocks(); + } + fireTableDataChanged(); + } + + public int getRowCount() { + return blocks.size(); + } + + public int getColumnCount() { + return COLUMN_NAMES.length; + } + + @Override + public String getColumnName(int column) { + return COLUMN_NAMES[column]; + } + + public Object getValueAt(int row, int column) { + BasicBlock block = blocks.get(row); + switch (column) { + case BLOCK_TABLE_NAME_COL_IDX: + return block.getName(); + case BLOCK_TABLE_BCI_COL_IDX: + return "[" + block.getFromBci() + ", " + block.getToBci() + "]"; + case BLOCK_TABLE_FLAGS_COL_IDX: + return formatFlags(block.getFlags()); + case BLOCK_TABLE_LOOP_DEPTH_COL_IDX: + return Integer.toString(block.getLoopDepth()); + case BLOCK_TABLE_LOOP_INDEX_COL_IDX: + return block.getLoopDepth() > 0 ? Integer.toString(block.getLoopIndex()) : ""; + case BLOCK_TABLE_DOMINATOR_COL_IDX: + return block.getDominator() != null ? block.getDominator().getName() : ""; + case BLOCK_TABLE_PREDECESSORS_COL_IDX: + return formatBlocks(block.getPredecessors()); + case BLOCK_TABLE_SUCCESSORS_COL_IDX: + return formatBlocks(block.getSuccessors()); + case BLOCK_TABLE_XHANDLERS_COL_IDX: + return formatBlocks(block.getXhandlers()); + case BLOCK_TABLE_PROBABILITY_COL_IDX: + return Double.isNaN(block.getProbability()) ? "" : (Double)block.getProbability(); + default: + throw new Error("invalid column"); + } + } + + @Override + public Class getColumnClass(int columnIndex) { + if (blocks.isEmpty()) { + return Object.class; + } + return getValueAt(0, columnIndex).getClass(); + } + + private String formatFlags(List flags) { + StringBuilder sb = new StringBuilder(); + String prefix = ""; + for (String flag : flags) { + sb.append(prefix).append(flag); + prefix = ", "; + } + return sb.toString(); + } + + private String formatBlocks(List blocks) { + StringBuilder sb = new StringBuilder(); + String prefix = ""; + for (BasicBlock block : blocks) { + sb.append(prefix).append(block.getName()); + prefix = ", "; + } + return sb.toString(); + } +} diff --git a/visualizer/C1Visualizer/BlockView/src/main/java/at/ssw/visualizer/block/view/BlockViewTopComponent.java b/visualizer/C1Visualizer/BlockView/src/main/java/at/ssw/visualizer/block/view/BlockViewTopComponent.java new file mode 100644 index 000000000000..8e0109534fda --- /dev/null +++ b/visualizer/C1Visualizer/BlockView/src/main/java/at/ssw/visualizer/block/view/BlockViewTopComponent.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.block.view; + +import at.ssw.visualizer.core.selection.Selection; +import at.ssw.visualizer.core.selection.SelectionManager; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import java.awt.BorderLayout; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.swing.BorderFactory; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.TableModel; +import javax.swing.table.TableRowSorter; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; + +/** + * TopComponent which displays the BlockView. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + */ +final class BlockViewTopComponent extends TopComponent { + private ControlFlowGraph curCFG; + private BasicBlock[] curBlocks; + + private JTable blockTable; + private BlockTableModel tableModel; + private boolean selectionUpdating; + + private BlockViewTopComponent() { + setName("Blocks"); + setToolTipText("List of Blocks"); + + tableModel = new BlockTableModel(); + blockTable = new JTable(tableModel); + blockTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + blockTable.setRowMargin(0); + blockTable.getColumnModel().setColumnMargin(0); + blockTable.setShowGrid(false); + blockTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + for (int i = 0; i < BlockTableModel.COLUMN_WIDTHS.length; i++) { + blockTable.getColumnModel().getColumn(i).setPreferredWidth(BlockTableModel.COLUMN_WIDTHS[i]); + } + blockTable.getSelectionModel().addListSelectionListener(listSelectionListener); + + // sorting + TableRowSorter sorter = new TableRowSorter(blockTable.getModel()); + sorter.setComparator(BlockTableModel.BLOCK_TABLE_NAME_COL_IDX, new Comparator() { + + @Override + public int compare(String block1, String block2) { + if (block1.charAt(0) == 'B' && block2.charAt(0)=='B') { + try { + return Integer.parseUnsignedInt(block1.substring(1)) - Integer.parseUnsignedInt(block2.substring(1)); + } catch(NumberFormatException e) { + // fall-back to string + } + } + return block1.compareTo(block2); + } + }); + blockTable.setRowSorter(sorter); + + JScrollPane scrollPane = new JScrollPane(blockTable); + scrollPane.setViewportBorder(BorderFactory.createEmptyBorder()); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + setLayout(new BorderLayout()); + add(scrollPane); + } + + @Override + protected void componentShowing() { + super.componentShowing(); + SelectionManager.getDefault().addChangeListener(selectionChangeListener); + updateContent(); + } + + @Override + protected void componentHidden() { + super.componentHidden(); + SelectionManager.getDefault().removeChangeListener(selectionChangeListener); + selectionUpdating = true; + curCFG = null; + curBlocks = null; + tableModel.setControlFlowGraph(null); + selectionUpdating = false; + } + + + private ChangeListener selectionChangeListener = new ChangeListener() { + public void stateChanged(ChangeEvent event) { + updateContent(); + } + }; + + protected void updateContent() { + if (selectionUpdating) { + return; + } + selectionUpdating = true; + Selection selection = SelectionManager.getDefault().getCurSelection(); + ControlFlowGraph newCFG = selection.get(ControlFlowGraph.class); + BasicBlock[] newBlocks = selection.get(BasicBlock[].class); + + if (curCFG != newCFG) { + // This resets a user-defined sorting. + tableModel.setControlFlowGraph(newCFG); + curBlocks = null; + } + + if (newBlocks != null) { + if(newBlocks.length == 0) { + blockTable.clearSelection(); + } else if (!Arrays.equals(curBlocks, newBlocks)) { + Map blockNames = new HashMap(); + for (BasicBlock block : newBlocks) { + blockNames.put(block.getName(), block); + } + + blockTable.clearSelection(); + for (int i = blockTable.getModel().getRowCount() - 1; i >= 0; i--) { + BasicBlock block = blockNames.get(blockTable.getValueAt(i, 0)); + if (block != null) { + blockTable.addRowSelectionInterval(i, i); + blockTable.scrollRectToVisible(blockTable.getCellRect(i, 0, true)); + } + } + } + } + curCFG = newCFG; + curBlocks = newBlocks; + selectionUpdating = false; + } + + + private ListSelectionListener listSelectionListener = new ListSelectionListener() { + public void valueChanged(ListSelectionEvent event) { + updateSelection(); + } + }; + + private void updateSelection() { + if (selectionUpdating) { + return; + } + selectionUpdating = true; + + List blocks = new ArrayList(); + for (int i = 0; i < blockTable.getModel().getRowCount(); i++) { + if (blockTable.getSelectionModel().isSelectedIndex(i)) { + blocks.add(curCFG.getBasicBlockByName((String) blockTable.getValueAt(i, 0))); + } + } + + curBlocks = blocks.toArray(new BasicBlock[blocks.size()]); + Selection selection = SelectionManager.getDefault().getCurSelection(); + selection.put(curBlocks); + + selectionUpdating = false; + } + + // + private static final String PREFERRED_ID = "BlockViewTopComponent"; + private static BlockViewTopComponent instance; + + public static synchronized BlockViewTopComponent getDefault() { + if (instance == null) { + instance = new BlockViewTopComponent(); + } + return instance; + } + + public static synchronized BlockViewTopComponent findInstance() { + return (BlockViewTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID); + } + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_ALWAYS; + } + + @Override + protected String preferredID() { + return PREFERRED_ID; + } + + @Override + public Object writeReplace() { + return new ResolvableHelper(); + } + + static final class ResolvableHelper implements Serializable { + private static final long serialVersionUID = 1L; + public Object readResolve() { + return BlockViewTopComponent.getDefault(); + } + } + // +} diff --git a/visualizer/C1Visualizer/BlockView/src/main/java/at/ssw/visualizer/block/view/ShowBlockViewAction.java b/visualizer/C1Visualizer/BlockView/src/main/java/at/ssw/visualizer/block/view/ShowBlockViewAction.java new file mode 100644 index 000000000000..053509a9a84a --- /dev/null +++ b/visualizer/C1Visualizer/BlockView/src/main/java/at/ssw/visualizer/block/view/ShowBlockViewAction.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.block.view; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.openide.windows.TopComponent; + +/** + * Action which shows BlockView component. + * + * @author Bernhard Stiftner + */ +public class ShowBlockViewAction extends AbstractAction { + public ShowBlockViewAction() { + super("Blocks View"); + } + + public void actionPerformed(ActionEvent event) { + TopComponent win = BlockViewTopComponent.findInstance(); + win.open(); + win.requestActive(); + } +} diff --git a/visualizer/C1Visualizer/BlockView/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/BlockView/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..e57c53fa1021 --- /dev/null +++ b/visualizer/C1Visualizer/BlockView/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.block.view +OpenIDE-Module-Layer: at/ssw/visualizer/block/view/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/block/view/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/BlockViewTopComponentSettings.xml b/visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/BlockViewTopComponentSettings.xml new file mode 100644 index 000000000000..e9cb6e95d844 --- /dev/null +++ b/visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/BlockViewTopComponentSettings.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/BlockViewTopComponentWstcref.xml b/visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/BlockViewTopComponentWstcref.xml new file mode 100644 index 000000000000..66cb2b958926 --- /dev/null +++ b/visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/BlockViewTopComponentWstcref.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/Bundle.properties b/visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/Bundle.properties new file mode 100644 index 000000000000..01077adde15c --- /dev/null +++ b/visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Block View diff --git a/visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/layer.xml b/visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/layer.xml new file mode 100644 index 000000000000..e0243e78c1f7 --- /dev/null +++ b/visualizer/C1Visualizer/BlockView/src/main/resources/at/ssw/visualizer/block/view/layer.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/BytecodeEditor/pom.xml b/visualizer/C1Visualizer/BytecodeEditor/pom.xml new file mode 100644 index 000000000000..737393dc56b0 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeEditor/pom.xml @@ -0,0 +1,128 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + BytecodeEditor + 1.13-SNAPSHOT + nbm + BytecodeEditor + + UTF-8 + + + + at.ssw.visualizer + VisualizerUI + ${project.version} + + + at.ssw.visualizer + BytecodeModel + ${project.version} + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + at.ssw.visualizer + TextEditor + ${project.version} + + + org.netbeans.api + org-netbeans-modules-editor + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-fold + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-lib + ${netbeans.version} + + + org.netbeans.api + org-openide-awt + ${netbeans.version} + + + org.netbeans.api + org-openide-nodes + ${netbeans.version} + + + org.netbeans.api + org-openide-text + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-util-ui + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.bc + at.ssw.visualizer.bc.model + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/BCEditor.java b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/BCEditor.java new file mode 100644 index 000000000000..b68c04331d0f --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/BCEditor.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc; + +import at.ssw.visualizer.texteditor.Editor; +import org.openide.windows.CloneableTopComponent; + +/** + * Editor associated to the BCEditorSupport. + * + * @author Alexander Reder + * @author Christian Wimmer + */ +public class BCEditor extends Editor { + + /** + * Creates a new CloneableEditor for the ClonableEditorSupport. + * + * @param support ClonableEditor to attach + */ + public BCEditor(BCEditorSupport support) { + super(support); + } + + @Override + protected CloneableTopComponent createClonedObject() { + BCEditor editor = new BCEditor((BCEditorSupport) cloneableEditorSupport()); + editor.setActivatedNodes(getActivatedNodes()); + return editor; + } +} diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/BCEditorKit.java b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/BCEditorKit.java new file mode 100644 index 000000000000..fd6fc653b1f1 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/BCEditorKit.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc; + +import at.ssw.visualizer.bc.model.BCScanner; +import at.ssw.visualizer.texteditor.EditorKit; +import javax.swing.text.Document; +import org.netbeans.editor.Syntax; + +/** + * A EditorKit for the bytecode, mime type text/x-compilation-bc, providing + * the syntax for the bytecode. + * + * @author Alexander Reder + */ +public class BCEditorKit extends EditorKit { + + @Override + public Syntax createSyntax(Document document) { + return new BCScanner(); + } + + /** + * Returns the mime type which identifies the bytecode for this EditorKit. + * + * @return the mime type from the EditorSupport (text/x-compilation-bc) + */ + @Override + public String getContentType() { + return BCEditorSupport.MIME_TYPE; + } +} diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/BCEditorSupport.java b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/BCEditorSupport.java new file mode 100644 index 000000000000..2f174f3f42b8 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/BCEditorSupport.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc; + +import at.ssw.visualizer.bc.icons.Icons; +import at.ssw.visualizer.bc.model.BCTextBuilder; +import at.ssw.visualizer.model.bc.Bytecodes; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.texteditor.EditorSupport; +import javax.swing.text.EditorKit; +import javax.swing.text.StyledDocument; +import org.openide.text.CloneableEditor; +import org.openide.util.ImageUtilities; + +/** + * Associates an Editor to a StyledDocument. + * + * @author Alexander Reder + * @author Christian Wimmer + */ +public class BCEditorSupport extends EditorSupport { + + public static final String MIME_TYPE = "text/x-compilation-bc"; + + private Bytecodes bytecodes; + + public BCEditorSupport(Bytecodes bytecodes) { + super(bytecodes.getControlFlowGraph()); + this.bytecodes = bytecodes; + this.text = new BCTextBuilder().buildDocument(cfg); + } + + public String getMimeType() { + return MIME_TYPE; + } + + public Bytecodes getBytecodes() { + return bytecodes; + } + + /** + * Returns the Editor associated to the EditorSupport. + * + * @return a new BCEditor + */ + @Override + protected CloneableEditor createCloneableEditor() { + return new BCEditor(this); + } + + /** + * Returns the document associated to the EditorSupport and the EditorKit. + * + * @param kit the EditorKit + * @return the new created styled document + */ + @Override + protected StyledDocument createStyledDocument(EditorKit kit) { + StyledDocument doc = super.createStyledDocument(kit); + doc.putProperty(Bytecodes.class, bytecodes); + doc.putProperty(ControlFlowGraph.class, bytecodes.getControlFlowGraph()); + return doc; + } + + /** + * Returns the name of the method which will be displayed on top of the + * editor window. + * + * @return the method name + */ + @Override + public String messageName() { + return bytecodes.getControlFlowGraph().getShortName(); + } + + @Override + protected String messageToolTip() { + return bytecodes.getControlFlowGraph().getName(); + } + + @Override + protected void initializeCloneableEditor(CloneableEditor editor) { + super.initializeCloneableEditor(editor); + editor.setIcon(ImageUtilities.loadImage(Icons.BYTECODE)); + } + +} diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/action/ShowBCEditorAction.java b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/action/ShowBCEditorAction.java new file mode 100644 index 000000000000..633f12424599 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/action/ShowBCEditorAction.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.action; + +import at.ssw.visualizer.bc.BCEditor; +import at.ssw.visualizer.bc.BCEditorSupport; +import at.ssw.visualizer.bc.icons.Icons; +import at.ssw.visualizer.bc.model.BytecodeModel; +import at.ssw.visualizer.core.focus.Focus; +import at.ssw.visualizer.model.bc.Bytecodes; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import javax.swing.JOptionPane; +import org.openide.nodes.Node; +import org.openide.util.HelpCtx; +import org.openide.util.Lookup; +import org.openide.util.actions.CookieAction; + +/** + * This class provides methods for the action to open the BytecodeEditor + * for an entry of the compilation menu. + * + * @author Alexander Reder + * @author Christian Wimmer + */ +public final class ShowBCEditorAction extends CookieAction { + /** + * Opens the BCEditor for the control flow graph of the active node. + * + * @param activatedNodes Nodes that are selected + */ + protected void performAction(Node[] activatedNodes) { + BytecodeModel bcModel = Lookup.getDefault().lookup(BytecodeModel.class); + ControlFlowGraph cfg = activatedNodes[0].getLookup().lookup(ControlFlowGraph.class); + Bytecodes bytecodes = bcModel.getBytecodes(cfg); + if (bytecodes == null) { + JOptionPane.showMessageDialog(null, bcModel.noBytecodesMsg(cfg), "No bytecodes available", JOptionPane.INFORMATION_MESSAGE); + return; + } + + if (!Focus.findEditor(BCEditor.class, bytecodes.getControlFlowGraph())) { + BCEditorSupport editor = new BCEditorSupport(bytecodes); + editor.open(); + } + } + + /** + * Returns how often an action can be performed on a selected node. + * + * @return it can be performed only one time. + */ + protected int mode() { + return CookieAction.MODE_EXACTLY_ONE; + } + + /** + * When should the BCEditor be available. + * + * @return returns true if exactely one BlockListBuilder node is selected + * or exactly one After Generation of HIR node is selected and there is + * only one BlockListerBuilder node, false otherwise. + */ + @Override + protected boolean enable(Node[] activatedNodes) { + if (!super.enable(activatedNodes)) { + return false; + } + BytecodeModel bcModel = Lookup.getDefault().lookup(BytecodeModel.class); + ControlFlowGraph cfg = activatedNodes[0].getLookup().lookup(ControlFlowGraph.class); + return bcModel.hasBytecodes(cfg); + } + + /** + * Returns the name of the action. + * + * @return name of the action + */ + public String getName() { + return "Open Bytecodes"; + } + + /** + * Returns the icon of the action. + * + * @return icon of the action + */ + @Override + protected String iconResource() { + return Icons.BYTECODE; + } + + /** + * Specfies the cookie classes where this action should be available. + * + * @return ControlFlowGraph.class + */ + protected Class[] cookieClasses() { + return new Class[]{ControlFlowGraph.class}; + } + + /** + * Returns the help context for this action. + * + * @return null, no help context available + */ + public HelpCtx getHelpCtx() { + return null; + } + + /** + * How this action should be performed. + * + * @return false, this action will be performed immediatly + */ + @Override + protected boolean asynchronous() { + return false; + } +} diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/icons/Icons.java b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/icons/Icons.java new file mode 100644 index 000000000000..99e696668ac0 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/icons/Icons.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.icons; + +/** + * + * @author Christian Wimmer + */ +public class Icons { + private static final String PATH = "at/ssw/visualizer/bc/icons/"; + + public static final String BYTECODE = PATH + "bytecode.gif"; +} diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCExample b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCExample new file mode 100644 index 000000000000..3d5acf4953bd --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCExample @@ -0,0 +1,18 @@ +Code(max_stack = 3, max_locals = 6, code_length = 60) + +B0 -> B1,B2 std +0: aload_0 +1: getfield java.lang.String.hash I (349) +4: istore_1 +5: iload_1 +6: ifne #58 +B3 <- B1,B4 -> B4,B5 plh +28: iload %5 +30: iload %4 +32: if_icmpge #53 +B4 <- B3 -> B3 +41: iinc %2 1 + + +Attribute(s) = +LineNumber(0, 1483), LineNumber(5, 1484), LineNumber(9, 1485), LineNumber(14, 1486) diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCScanner.java b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCScanner.java new file mode 100644 index 000000000000..0adfc419777a --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCScanner.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.model; + +import at.ssw.visualizer.texteditor.model.Scanner; +import org.netbeans.editor.TokenID; + +/** + * This class is used for parsing the bytecode text. + * + * @author Alexander Reder + * @author Christian Wimmer + */ +public class BCScanner extends Scanner { + public BCScanner() { + super("\n\r\t ,:", BCTokenContext.contextPath); + } + + private boolean isBlock() { + return expectChar('B') && expectChar(DIGIT) && expectChars(DIGIT) && expectEnd(); + } + + private boolean isBciDef() { + return expectChar(DIGIT) && expectChars(DIGIT) && expectEnd(':'); + } + + private boolean isBciRef() { + return expectChar('#') && expectChar(DIGIT) && expectChars(DIGIT) && expectEnd(); + } + + private boolean isVarRef() { + return expectChar('%') && expectChar(DIGIT) && expectChars(DIGIT) && expectEnd(); + } + + private boolean isBcDescription() { + return beforeChar(':') && expectChar(LETTER) && expectChars(LETTER_DIGIT) && expectEnd(); + } + + @Override + protected TokenID parseToken() { + findTokenBegin(); + if (ch == EOF) { + return BCTokenContext.EOF_TOKEN; + } else if (isWhitespace()) { + return BCTokenContext.WHITESPACE_TOKEN; + } else if (isBlock()) { + return BCTokenContext.BLOCK_TOKEN; + } else if (isBciDef() || isBciRef()) { + return BCTokenContext.BCI_TOKEN; + } else if (isVarRef()) { + return BCTokenContext.VAR_REFERENCE_TOKEN; + } else if (isBcDescription()) { + return BCTokenContext.BC_DESCRIPTION_TOKEN; + } else { + readToWhitespace(); + return BCTokenContext.OTHER_TOKEN; + } + } + +} diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCTextBuilder.java b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCTextBuilder.java new file mode 100644 index 000000000000..d3787a02f818 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCTextBuilder.java @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.model; + +import at.ssw.visualizer.model.bc.Bytecodes; +import at.ssw.visualizer.bc.BCEditorSupport; +import at.ssw.visualizer.model.Compilation; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.texteditor.model.BlockRegion; +import at.ssw.visualizer.texteditor.model.FoldingRegion; +import at.ssw.visualizer.texteditor.model.HoverParser; +import at.ssw.visualizer.texteditor.model.Text; +import at.ssw.visualizer.texteditor.model.TextBuilder; +import at.ssw.visualizer.texteditor.model.TextRegion; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.netbeans.api.editor.fold.FoldType; +import org.netbeans.editor.TokenID; +import org.openide.util.Lookup; + +/** + * Builds the text containing the bytecode. + * + * @author Alexander Reder + */ +public class BCTextBuilder extends TextBuilder { + + private static final FoldType KIND_BLOCK = new FoldType("..."); + + private static final String BLOCK_LIST_PREFIX = "BlockListBuilder "; + + public BCTextBuilder() { + super(); + scanner = new BCScanner(); + } + + @Override + public Text buildDocument(ControlFlowGraph cfg) { + BytecodeModel bcModel = Lookup.getDefault().lookup(BytecodeModel.class); + Bytecodes bytecodes = bcModel.getBytecodes(cfg); + bytecodes.parseBytecodes(); + + Compilation compilation = cfg.getCompilation(); + DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); + + String name = cfg.getName(); + if (name.startsWith(BLOCK_LIST_PREFIX)) { + name = name.substring(BLOCK_LIST_PREFIX.length()); + } + text.append(name).append("\n"); + text.append(dateFormat.format(compilation.getDate())).append("\n\n"); + + BasicBlock[] sortedBlocks = sortBlocks(cfg.getBasicBlocks()); + if (sortedBlocks.length == 0) { + appendAll(bytecodes); + } else { + for(int i = 0; i < sortedBlocks.length; i++) { + appendBlock(sortedBlocks, bytecodes, i); + } + } + text.append("\n\n").append(bytecodes.getEpilogue()); + return buildText(cfg, BCEditorSupport.MIME_TYPE); + } + + public String buildView(ControlFlowGraph cfg, BasicBlock[] basicBlocks) { + BytecodeModel bcModel = Lookup.getDefault().lookup(BytecodeModel.class); + Bytecodes bytecodes = bcModel.getBytecodes(cfg); + if(bytecodes == null) { + return "No bytecodes available\n"; + } + StringBuilder view = new StringBuilder(1024); + buildDocument(cfg); + for(BasicBlock b : basicBlocks) { + BlockRegion br = blocks.get(b); + view.append(text.substring(br.getStart(), br.getEnd())); + } + return view.toString(); + } + + private void appendAll(Bytecodes bytecodes) { + int fromBCI = 0; + int toBCI = Integer.MAX_VALUE; + String[] bcs = bytecodes.getBytecodes(fromBCI, toBCI).split("\n"); + for(String s : bcs) { + appendBytecode(null, s); + } + } + + private void appendBlock(BasicBlock[] basicBlocks, Bytecodes bytecodes, int index) { + int start = text.length(); + int fromBCI = basicBlocks[index].getFromBci(); + int toBCI = Integer.MAX_VALUE; + if(basicBlocks.length > (index + 1) && basicBlocks[index + 1].getFromBci() >= 0) { + toBCI = basicBlocks[index + 1].getFromBci(); + } + appendBlockDetails(basicBlocks[index]); + text.append("\n"); + int bytecodesStart = text.length(); + String[] bcs = bytecodes.getBytecodes(fromBCI, toBCI).split("\n"); + for(String s : bcs) { + appendBytecode(basicBlocks[index], s); + } + blocks.put(basicBlocks[index], + new BlockRegion(basicBlocks[index], start, text.length(), start, start + basicBlocks[index].getName().length())); + foldingRegions.add(new FoldingRegion(KIND_BLOCK, bytecodesStart - 1, text.length() - 1, false)); + hyperlinks.put(basicBlocks[index].getName(), new TextRegion(start, start + basicBlocks[index].getName().length())); + } + + private void appendBytecode(BasicBlock block, String bytecode) { + int start = text.length(); + HoverParser p = new HoverParser(bytecode); + while (p.hasNext()) { + int pstart = text.length(); + text.append(p.next()); + if (p.getHover() != null) { + regionHovers.put(new TextRegion(pstart, text.length()), p.getHover()); + } + } + text.append("\n"); + scanner.setText(bytecode, 0, bytecode.length()); + TokenID token = scanner.nextToken(); + String tokenString; + while(token != null && token.getNumericID() != BCTokenContext.EOF_TOKEN_ID) { + tokenString = scanner.getTokenString(); + if(token.getNumericID() == BCTokenContext.BCI_TOKEN_ID) { + if(scanner.getTokenString().startsWith("#")) { + addReference(block, tokenString, bytecode); + addReference(block, tokenString.substring(1, tokenString.length()), bytecode); + } else { + hoverKeys.add(tokenString); + hoverKeys.add('#' + tokenString); + addDefinition(block, tokenString, bytecode); + addDefinition(block, '#' + tokenString, bytecode); + hyperlinks.put('#' + tokenString, new TextRegion(start + scanner.getTokenOffset(), start + scanner.getOffset())); + } + } else if(token.getNumericID() == BCTokenContext.VAR_REFERENCE_TOKEN_ID) { + hoverKeys.add(tokenString); + addReference(block, tokenString, bytecode); + } + token = scanner.nextToken(); + } + } + + private void addReference(BasicBlock block, String key, String s) { + if (block == null) { + return; + } + if(!hoverReferences.containsKey(key)) { + hoverReferences.put(key, new ArrayList()); + } + hoverReferences.get(key).add(block.getName() + ":\t" + s); + } + + private void addDefinition(BasicBlock block, String key, String s) { + if (block == null) { + return; + } + hoverDefinitions.put(key, block.getName() + ":\t" + s); + } + + @Override + protected void buildHighlighting() { + scanner.setText(text.toString(), 0, text.length()); + TokenID token = scanner.nextToken(); + Map> highlightings = new HashMap>(); + while(token != null && token.getNumericID() != BCTokenContext.EOF_TOKEN_ID) { + String key = scanner.getTokenString(); + switch(token.getNumericID()) { + case BCTokenContext.BCI_TOKEN_ID: + if(key.startsWith("#")) { + key = key.substring(1, key.length()); + } + List tr = new ArrayList(); + if(!highlightings.containsKey(key)) { + highlightings.put(key, tr); + highlightings.put('#' + key, tr); + } + highlightings.get(key).add(new TextRegion(scanner.getTokenOffset(), scanner.getOffset())); + break; + case BCTokenContext.BLOCK_TOKEN_ID: + case BCTokenContext.VAR_REFERENCE_TOKEN_ID: + if(!highlightings.containsKey(key)) { + highlightings.put(key, new ArrayList()); + } + highlightings.get(key).add(new TextRegion(scanner.getTokenOffset(), scanner.getOffset())); + break; + } + token = scanner.nextToken(); + } + for(String key : highlightings.keySet()) { + List regions = highlightings.get(key); + highlighting.put(key, regions.toArray(new TextRegion[regions.size()])); + } + } + + /** + * Sorts the blocks on the basis of the starting BCI. BCIs < 0 will be + * placed at the end of the array. + * + * @param blocks array containing the unsorted blocks + * @return sorted array of blocks + */ + protected BasicBlock[] sortBlocks(List blocks) { + List blockList = new ArrayList(blocks); + Collections.sort(blockList, new Comparator() { + + public int compare(BasicBlock b1, BasicBlock b2) { + if (b1.getFromBci() >= 0 && b2.getFromBci() < 0) { + return -1; + } + if (b1.getFromBci() < 0 && b2.getFromBci() >= 0) { + return 1; + } + if(b1 != b2 && b1.getFromBci() == b2.getFromBci()) { + return -1; + } + return b1.getFromBci() - b2.getFromBci(); + } + }); + return blockList.toArray(new BasicBlock[blocks.size()]); + } + +} diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCTokenContext.java b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCTokenContext.java new file mode 100644 index 000000000000..718051890be9 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCTokenContext.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.model; + +import org.netbeans.editor.BaseTokenID; +import org.netbeans.editor.TokenContext; +import org.netbeans.editor.TokenContextPath; +import org.netbeans.editor.TokenID; + +/** + * The token context for the scanner. + * + * @author Alexander Reder + */ +public class BCTokenContext extends TokenContext { + + public static final int EOF_TOKEN_ID = -1; + public static final int WHITESPACE_TOKEN_ID = -2; + public static final int OTHER_TOKEN_ID = -3; + public static final int BCI_TOKEN_ID = 4; + public static final int BC_DESCRIPTION_TOKEN_ID = 5; + public static final int VAR_REFERENCE_TOKEN_ID = 6; + public static final int BLOCK_TOKEN_ID = 7; + + public static final TokenID EOF_TOKEN = new BaseTokenID("eof", EOF_TOKEN_ID); + public static final TokenID WHITESPACE_TOKEN = new BaseTokenID("whitespace", WHITESPACE_TOKEN_ID); + public static final TokenID OTHER_TOKEN = new BaseTokenID("other", OTHER_TOKEN_ID); + public static final TokenID BCI_TOKEN = new BaseTokenID("bci", BCI_TOKEN_ID); + public static final TokenID BC_DESCRIPTION_TOKEN = new BaseTokenID("bytecode_description", BC_DESCRIPTION_TOKEN_ID); + public static final TokenID VAR_REFERENCE_TOKEN = new BaseTokenID("var_reference", VAR_REFERENCE_TOKEN_ID); + public static final TokenID BLOCK_TOKEN = new BaseTokenID("block", BLOCK_TOKEN_ID); + + public static final BCTokenContext context = new BCTokenContext(); + public static final TokenContextPath contextPath = context.getContextPath(); + + /** + * Initializes the token context with the prefix "bc-" and all available + * tokens. + */ + private BCTokenContext() { + super("bc-"); + addTokenID(WHITESPACE_TOKEN); + addTokenID(OTHER_TOKEN); + addTokenID(BCI_TOKEN); + addTokenID(BC_DESCRIPTION_TOKEN); + addTokenID(VAR_REFERENCE_TOKEN); + addTokenID(BLOCK_TOKEN); + } +} diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/BytecodeEditor/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..6dc884109add --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeEditor/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.bc +OpenIDE-Module-Layer: at/ssw/visualizer/bc/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/bc/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/Bundle.properties b/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/Bundle.properties new file mode 100644 index 000000000000..e1b692d69fb8 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/Bundle.properties @@ -0,0 +1,12 @@ +OpenIDE-Module-Name=Bytecode Editor + +text/x-compilation-bc=Bytecodes +bc-bci=Bytecode index +bc-bci_reference=Bytecode index reference +bc-bytecode_description=Bytecode description +bc-var_reference=Variable reference +bc-block=Block +bc-block_reference=Block reference +bc-whitespace=Whitespace +bc-other=Other +bc-default=Default diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/icons/bytecode.gif b/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/icons/bytecode.gif new file mode 100644 index 0000000000000000000000000000000000000000..daa9302b4d10d43d3970f9851cdeae0ffa4fc7e1 GIT binary patch literal 132 zcmZ?wbhEHb6krfw*v!B%jX`4O%uP#|?%TZi%$YOK?%)6U{Q3ViYmfc^|Np>&0}Kod zKn9S61I3>#j0_Ci3_2hYkQodth6N`*SMRlWeO5ano})!hXqHApz$?pb+M@c}Ntbmj bnx36o=5Y1>RH?HnTcbbS$h1-yWUvMR^ literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/layer.xml b/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/layer.xml new file mode 100644 index 000000000000..16f1504bba06 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/layer.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/model/NetBeans-BC-fontsColors.xml b/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/model/NetBeans-BC-fontsColors.xml new file mode 100644 index 000000000000..9067fc09e5a7 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/model/NetBeans-BC-fontsColors.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/BytecodeModel/pom.xml b/visualizer/C1Visualizer/BytecodeModel/pom.xml new file mode 100644 index 000000000000..d1a72b3b3fa1 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/pom.xml @@ -0,0 +1,97 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + BytecodeModel + 1.13-SNAPSHOT + nbm + BytecodeModel + + UTF-8 + + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + org.apache.bcel + bcel + 5.2 + + + org.netbeans.api + org-netbeans-modules-options-api + ${netbeans.version} + + + org.netbeans.api + org-openide-awt + ${netbeans.version} + + + org.netbeans.api + org-openide-filesystems + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-util-ui + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.bc.model + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/model/BytecodeModel.java b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/model/BytecodeModel.java new file mode 100644 index 000000000000..8f5c6f1285ad --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/model/BytecodeModel.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.model; + +import at.ssw.visualizer.model.bc.Bytecodes; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; + +/** + * Entry point for the public bytecodes data model. + * + * @author Christian Wimmer + */ +public interface BytecodeModel { + /** + * Checks if bytecodes can be available for a control flow graph, i.e. if + * the control flow graph is either from the early bytecode parsing phase + * of the compiler, or no method inlining was performed. + * + * This does not imply that {link #getBytecodes()} returns true for this + * control flow graph. It is possible that the class is not in the + * classpath specified in the visualizer options. + * + * @return returns true if a BlockListBuilder node is selected + * or a After Generation of HIR node is selected and there is + * only one BlockListerBuilder node, false otherwise. + */ + public boolean hasBytecodes(ControlFlowGraph cfg); + + /** + * Gets the bytecodes for a control flow graph. + * + * @return the bytecodes, or null if not available. + */ + public Bytecodes getBytecodes(ControlFlowGraph cfg); + + /** + * Returns a human-readable message that explains why no bytecodes are + * available for the specified control flow graph. + */ + public String noBytecodesMsg(ControlFlowGraph cfg); +} diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/BytecodeModelImpl.java b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/BytecodeModelImpl.java new file mode 100644 index 000000000000..a36468c84826 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/BytecodeModelImpl.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.modelimpl; + +import at.ssw.visualizer.bc.model.BytecodeModel; +import at.ssw.visualizer.model.bc.Bytecodes; +import at.ssw.visualizer.model.CompilationElement; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JOptionPane; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.Repository; + +/** + * This class holds the classpaths and the method bytecodes. + * + * @author Alexander Reder + * @author Christian Wimmer + */ +public class BytecodeModelImpl implements BytecodeModel { + + private String[] classPaths; + private Map loadedBytecodes; + + /** + * Initializes a BytecodeModel. + */ + public BytecodeModelImpl() { + loadClassPaths(); + + // Use a synchronized map to avoid all possible threading issues. + // Use a weak map because elements are never removed explicitly. + loadedBytecodes = Collections.synchronizedMap(new WeakHashMap()); + } + + /** The prefix of all control flow graphs in the early bytecode parsing phase of the compiler. */ + private static final String BLOCK_LIST_PREFIX = "BlockListBuilder "; + + private ControlFlowGraph getBlockListCFG(ControlFlowGraph cfg) { + if (cfg.getBytecodes() != null) { + return cfg; + } + if (cfg.getName().startsWith(BLOCK_LIST_PREFIX)) { + return cfg; + } + + ControlFlowGraph result = null; + for (CompilationElement ce : cfg.getCompilation().getElements()) { + if (ce instanceof ControlFlowGraph && ce.getName().startsWith(BLOCK_LIST_PREFIX)) { + if (result == null) { + result = (ControlFlowGraph) ce; + if (result.getElements().size() > 0) { + // Child elements are present, so methods were inlined. + return null; + } + } else { + // More than one BlockListBuilder, so methods were inlined. + return null; + } + } + } + return result; + } + + private String getMethodName(ControlFlowGraph cfg) { + assert cfg.getName().startsWith(BLOCK_LIST_PREFIX); + return cfg.getName().substring(BLOCK_LIST_PREFIX.length()); + } + + + public boolean hasBytecodes(ControlFlowGraph cfg) { + return getBlockListCFG(cfg) != null; + } +/* + public boolean bytecodesLoaded(ControlFlowGraph cfg) { + return getBytecodes(cfg) != null; + } +*/ + public String noBytecodesMsg(ControlFlowGraph cfg) { + StringBuilder result = new StringBuilder(); + result.append("No bytecodes available for ").append(cfg.getCompilation().getName()).append(" - ").append(cfg.getName()); + if (!hasBytecodes(cfg)) { + result.append("\nThe compiler inlined methods during compilation, so there is no single method to take the bytecodes from."); + } else { + result.append("\nThe method is not present in the classpath. Configure the claspath using Tools->Options."); + } + return result.toString(); + } + + public Bytecodes getBytecodes(ControlFlowGraph cfg) { + Bytecodes result = loadedBytecodes.get(cfg); + if (result != null) { + return result; + } + + cfg = getBlockListCFG(cfg); + if (cfg == null) { + return null; + } + + if (cfg.getBytecodes() != null) { + return cfg.getBytecodes(); + } + + String methodName = getMethodName(cfg); + List mbcs = BytecodesParser.readMethod(cfg, methodName, classPaths); + if (mbcs.size() == 0) { + return null; + } else if (mbcs.size() == 1) { + result = mbcs.get(0); + } else { + result = (Bytecodes) JOptionPane.showInputDialog(null, "Ambiguous methodname: \n" + methodName, "Select method", JOptionPane.QUESTION_MESSAGE, null, mbcs.toArray(), mbcs.get(0)); + if (result == null) { + return null; + } + } + + loadedBytecodes.put(cfg, result); + return result; + } + + private FileObject getOptionLocation() { + return Repository.getDefault().getDefaultFileSystem().findResource("at-ssw-visualizer-bc/classpaths"); + } + + /** + * Loads the classpath information from the system filesystem. + */ + private void loadClassPaths() { + List data = new ArrayList(); + try { + InputStream stream = getOptionLocation().getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + + String cp = reader.readLine(); + while (cp != null) { + data.add(cp); + cp = reader.readLine(); + } + reader.close(); + } catch (IOException ex) { + Logger log = Logger.getLogger(BytecodeModel.class.getName()); + log.log(Level.SEVERE, ex.getMessage(), ex); + } + + if (data.size() > 0) { + classPaths = data.toArray(new String[data.size()]); + } else { + classPaths = getDefaultClassPaths(); + } + } + + /** + * Stores the classpath infotmation to the system filesystem. + */ + private void saveClassPaths() { + try { + OutputStream stream = getOptionLocation().getOutputStream(); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream)); + + for (String cp : classPaths) { + writer.write(cp); + writer.newLine(); + } + writer.close(); + } catch (IOException ex) { + Logger log = Logger.getLogger(BytecodeModel.class.getName()); + log.log(Level.SEVERE, ex.getMessage(), ex); + } + } + + public String[] getDefaultClassPaths() { + return System.getProperty("sun.boot.class.path", "").split(System.getProperty("path.separator")); + } + + /** + * Returns an array of all classpaths stored in the model. + * + * @return String array with all classpaths + */ + public String[] getClassPaths() { + return classPaths; + } + + public void setClassPaths(String[] classPaths) { + this.classPaths = classPaths; + + loadedBytecodes.clear(); + saveClassPaths(); + } +} diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/BytecodesImpl.java b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/BytecodesImpl.java new file mode 100644 index 000000000000..96415af3bcfa --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/BytecodesImpl.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.modelimpl; + +import at.ssw.visualizer.model.bc.Bytecodes; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import java.util.SortedMap; + +/** + * This class holds the bytecode of a method and provides severel methods + * accessing the details. + * + * @author Alexander Reder + * @author Christian Wimmer + */ +public class BytecodesImpl implements Bytecodes { + + private ControlFlowGraph controlFlowGraph; + private String name; + private String shortName; + + private String prolog; + private String attributes; + private SortedMap byteCodes; + + public BytecodesImpl(ControlFlowGraph controlFlowGraph, String name, String shortName, String prolog, String attributes, SortedMap byteCodes) { + this.controlFlowGraph = controlFlowGraph; + this.name = name; + this.shortName = shortName; + this.prolog = prolog; + this.attributes = attributes; + this.byteCodes = byteCodes; + } + + @Override + public ControlFlowGraph getControlFlowGraph() { + return controlFlowGraph; + } + + @Override + public void parseBytecodes() { + // Nothing to parse + } + + @Override + public String getBytecodes(int fromBCI, int toBCI) { + StringBuilder sb = new StringBuilder(); + for (Integer key : byteCodes.subMap(fromBCI, toBCI).keySet()) { + String keyString = Integer.toString(key); + sb.append(keyString).append(":"); + sb.append(" ".substring(Math.min(keyString.length(), 4))); + sb.append(byteCodes.get(key)); + sb.append("\n"); + } + return sb.toString(); + } + + @Override + public String getEpilogue() { + return attributes; + } + + @Override + public String toString() { + return name; + } +} diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/BytecodesParser.java b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/BytecodesParser.java new file mode 100644 index 000000000000..accdc8b9d3f2 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/BytecodesParser.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.modelimpl; + +import at.ssw.visualizer.model.bc.Bytecodes; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.apache.bcel.classfile.ClassParser; +import org.apache.bcel.classfile.Code; +import org.apache.bcel.classfile.JavaClass; +import org.apache.bcel.classfile.Method; +import org.apache.bcel.generic.Type; + +/** + * + * @author Alexander Reder + * @author Christian Wimmer + */ +public class BytecodesParser { + + private BytecodesParser() { + } + + /** + * Trys to read the bytecode of the given method name. + * + * @param method full name of the method + */ + public static List readMethod(ControlFlowGraph cfg, String method, String[] classPaths) { + List result = new ArrayList(); + + MethodName methodName = MethodName.parse(method); + if (methodName == null) { + return result; + } + + for (String classPath : classPaths) { + try { + ClassParser cp = locateClass(methodName, classPath); + if (cp != null) { + JavaClass c = cp.parse(); + for (Method m : c.getMethods()) { + if (methodName.matches(m)) { + result.add(readBytecodes(cfg, methodName, m)); + } + } + } + } catch (IOException ex) { + Logger log = Logger.getLogger(BytecodesParser.class.getName()); + log.log(Level.INFO, ex.getMessage(), ex); + } + } + return result; + } + + private static ClassParser locateClass(MethodName methodName, String classPath) throws IOException { + File baseFile = new File(classPath); + if (!baseFile.exists()) { + return null; + } + + if (baseFile.isDirectory()) { + String className = methodName.className.replace('.', File.separatorChar) + ".class"; + File classFile = new File(baseFile, className); + if (classFile.exists()) { + return new ClassParser(new FileInputStream(classFile), className); + } + + } else { + String className = methodName.className.replace('.', '/') + ".class"; + ZipFile zipFile = new ZipFile(baseFile); + ZipEntry zipEntry = zipFile.getEntry(className); + if (zipEntry != null) { + return new ClassParser(zipFile.getInputStream(zipEntry), className); + } + } + return null; + } + + /** + * Parses the code string and generates the prolog, attributeslist and the + * hashmap for the bytecode. + */ + private static Bytecodes readBytecodes(ControlFlowGraph cfg, MethodName methodName, Method method) { + Code code = method.getCode(); + Scanner bcScanner = new Scanner(code.toString()); + + String prolog = ""; + String attributes = ""; + SortedMap byteCodes = new TreeMap(); + + bcScanner.useDelimiter("\n"); + while (bcScanner.hasNext()) { + String codeLine = bcScanner.next(); + if (codeLine.startsWith("Code")) { + prolog = codeLine; + } else if (codeLine.length() > 0 && codeLine.charAt(0) >= '0' && codeLine.charAt(0) <= '9' && codeLine.indexOf(':') > 0) { + Scanner codeLineScanner = new Scanner(codeLine); + codeLineScanner.useDelimiter(":\\s*"); +// try { + int key = Integer.parseInt(codeLineScanner.next()); + byteCodes.put(key, codeLineScanner.next()); +/* } catch (NumberFormatException ex) { + System.err.println(codeLine); + } catch (NoSuchElementException ex) { + System.err.println(codeLine); + } + */ + codeLineScanner.close(); + } else if (codeLine.length() > 0 && codeLine.charAt(0) != ' ') { + attributes = attributes + codeLine + "\n"; + } + } + bcScanner.close(); + + StringBuilder sb = new StringBuilder(); + sb.append(method.getReturnType()).append(" "); + sb.append(methodName.className); + sb.append(".").append(method.getName()).append("("); + boolean sep = false; + for (Type t : method.getArgumentTypes()) { + if (sep) { + sb.append(", "); + } + sb.append(t); + sep = true; + } + sb.append(")"); + String name = sb.toString(); + + sb = new StringBuilder(); + int point = methodName.className.lastIndexOf('.'); + if (point > 0) { + sb.append(methodName.className.substring(point + 1)); + } else { + sb.append(methodName.className); + } + sb.append(".").append(method.getName()); + String shortName = sb.toString(); + + return new BytecodesImpl(cfg, name, shortName, prolog, attributes, byteCodes); + } +} diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/MethodName.java b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/MethodName.java new file mode 100644 index 000000000000..8d3267f29063 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/modelimpl/MethodName.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.modelimpl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.bcel.classfile.Method; +import org.apache.bcel.generic.Type; + +/** + * This class holds informations like package, class, arguments, return values + * and name of a method. + * + * @author Alexander Reder + * @author Christian Wimmer + */ +public class MethodName { + + public String methodModifier; + public Type returnValType; + public String className; + public String methodName; + public List arguments; + + private MethodName() { + // Prevent creation from outside. + } + + /** + * Initializes a new MethodName and parses the full name. + * + * @param name full name of the method + */ + public static MethodName parse(String name) { + Scanner methodScanner = new Scanner(name); + methodScanner.useDelimiter("\\s|\\,\\s|\\(|\\)$"); + try { + MethodName result = new MethodName(); + result.methodModifier = methodScanner.next(); + result.returnValType = getTypeOf(methodScanner.next()); + String[] classMethodName = methodScanner.next().split("\\."); + StringBuilder pkgClass = new StringBuilder(); + pkgClass.append(classMethodName[0]); + for(int i = 1; i < classMethodName.length - 1; i++) { + pkgClass.append("."); + pkgClass.append(classMethodName[i]); + } + result.className = pkgClass.toString(); + result.methodName = classMethodName[classMethodName.length - 1]; + result.arguments = new ArrayList(); + String argument; + while(methodScanner.hasNext()) { + argument = methodScanner.next(); + if(!argument.equals("")) { + result.arguments.add(getTypeOf(argument)); + } + } + return result; + } catch (Exception ex) { + Logger log = Logger.getLogger(BytecodesParser.class.getName()); + log.log(Level.WARNING, ex.getMessage(), ex); + return null; + } finally { + methodScanner.close(); + } + } + + /** + * Checks if the specified method has the same signature as defined by this + * parsed method name. + */ + public boolean matches(Method m) { + if (!methodName.equals(m.getName())) { + return false; + } + if (returnValType != Type.UNKNOWN && returnValType != m.getReturnType()) { + return false; + } + if (arguments.size() != m.getArgumentTypes().length) { + return false; + } + for(int i = 0; i < arguments.size(); i++) { + if (arguments.get(i) != Type.UNKNOWN && arguments.get(i) != m.getArgumentTypes()[i]) { + return false; + } + } + return true; + } + + /** + * Translates the String representation of a type into a Type. + * + * @param type String representation of the type + * @return Type of the Stringrepresentation + */ + private static Type getTypeOf(String type) { + if(type.equals("void")) { + return Type.VOID; + } else if(type.equals("jint")) { + return Type.INT; + } else if(type.equals("jshort")) { + return Type.SHORT; + } else if(type.equals("jlong")) { + return Type.LONG; + } else if(type.equals("jchar")) { + return Type.CHAR; + } else if(type.equals("jboolean")) { + return Type.BOOLEAN; + } else if(type.equals("jdouble")) { + return Type.DOUBLE; + } else if(type.equals("jfloat")) { + return Type.FLOAT; + } else { + return Type.UNKNOWN; + } + } +} diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BC-classpaths b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BC-classpaths new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptionPanel.form b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptionPanel.form new file mode 100644 index 000000000000..de6db176c838 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptionPanel.form @@ -0,0 +1,102 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptionPanel.java b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptionPanel.java new file mode 100644 index 000000000000..fa9e46680b59 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptionPanel.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.options; + +import at.ssw.visualizer.bc.model.BytecodeModel; +import at.ssw.visualizer.bc.modelimpl.BytecodeModelImpl; +import javax.swing.DefaultListModel; +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileNameExtensionFilter; +import org.openide.util.Lookup; + +/** + * The panel for setting the classpaths. + * + * @author Alexander Reder + * @author Christian Wimmer + */ +final class BCOptionPanel extends javax.swing.JPanel { + + private final BCOptionsPanelController controller; + private final BytecodeModelImpl bytecodeModel; + private final DefaultListModel listModel; + + public BCOptionPanel(BCOptionsPanelController controller) { + this.controller = controller; + initComponents(); + + bytecodeModel = (BytecodeModelImpl) Lookup.getDefault().lookup(BytecodeModel.class); + listModel = new DefaultListModel(); + classpathList.setModel(listModel); + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + classpathScrollPane = new javax.swing.JScrollPane(); + classpathList = new javax.swing.JList(); + addPathButton = new javax.swing.JButton(); + removeButton = new javax.swing.JButton(); + addJarZipButton = new javax.swing.JButton(); + addDefaultClasspath = new javax.swing.JButton(); + + classpathList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + classpathScrollPane.setViewportView(classpathList); + + org.openide.awt.Mnemonics.setLocalizedText(addPathButton, "Add folder"); + addPathButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + addPathButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(removeButton, "Remove"); + removeButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + removeButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(addJarZipButton, "Add Jar/Zip file"); + addJarZipButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + addJarZipButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(addDefaultClasspath, "Add default classpath"); + addDefaultClasspath.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + addDefaultClasspathActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addComponent(classpathScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 309, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(addJarZipButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(addDefaultClasspath, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(removeButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(addPathButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {addDefaultClasspath, addJarZipButton, removeButton}); + + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(addPathButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(addJarZipButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(addDefaultClasspath) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(removeButton)) + .addComponent(classpathScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 202, Short.MAX_VALUE)) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void addDefaultClasspathActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addDefaultClasspathActionPerformed + for (String cp : bytecodeModel.getDefaultClassPaths()) { + listModel.addElement(cp); + controller.changed(); + classpathList.setSelectedIndex(listModel.getSize() - 1); + } + }//GEN-LAST:event_addDefaultClasspathActionPerformed + + private void addJarZipButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addJarZipButtonActionPerformed + JFileChooser chooser = new JFileChooser(); + chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + chooser.setFileFilter(new FileNameExtensionFilter("Bytecode archives", "jar", "zip")); + if (chooser.showOpenDialog(getParent()) == JFileChooser.APPROVE_OPTION) { + listModel.addElement(chooser.getSelectedFile().getAbsolutePath()); + controller.changed(); + classpathList.setSelectedIndex(listModel.getSize() - 1); + } + }//GEN-LAST:event_addJarZipButtonActionPerformed + + private void removeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeButtonActionPerformed + int idx = classpathList.getSelectedIndex(); + if (idx >= 0 && idx < listModel.getSize()) { + listModel.remove(idx); + controller.changed(); + if (listModel.size() > 0) { + classpathList.setSelectedIndex(Math.max(0, idx - 1)); + } + } + }//GEN-LAST:event_removeButtonActionPerformed + + private void addPathButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addPathButtonActionPerformed + JFileChooser chooser = new JFileChooser(); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (chooser.showOpenDialog(getParent()) == JFileChooser.APPROVE_OPTION) { + listModel.addElement(chooser.getSelectedFile().getAbsolutePath()); + controller.changed(); + classpathList.setSelectedIndex(listModel.getSize() - 1); + } + }//GEN-LAST:event_addPathButtonActionPerformed + + public void update() { + listModel.clear(); + for (String cp : bytecodeModel.getClassPaths()) { + listModel.addElement(cp); + } + } + + public void applyChanges() { + String[] data = new String[listModel.getSize()]; + listModel.copyInto(data); + bytecodeModel.setClassPaths(data); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton addDefaultClasspath; + private javax.swing.JButton addJarZipButton; + private javax.swing.JButton addPathButton; + private javax.swing.JList classpathList; + private javax.swing.JScrollPane classpathScrollPane; + private javax.swing.JButton removeButton; + // End of variables declaration//GEN-END:variables +} diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptions.java b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptions.java new file mode 100644 index 000000000000..0a1aa80864e1 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptions.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.options; + +import at.ssw.visualizer.bc.options.icons.Icons; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import org.netbeans.spi.options.OptionsCategory; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.ImageUtilities; + +/** + * This class configures the OptionPanel. + * + * @author Alexander Reder + */ +public final class BCOptions extends OptionsCategory { + + public OptionsPanelController create() { + return new BCOptionsPanelController(); + } + + public String getCategoryName() { + return "Bytecodes"; + } + + public String getTitle() { + return "Bytecodes"; + } + + @Override + public Icon getIcon() { + return new ImageIcon(ImageUtilities.loadImage(Icons.BC_OPTION)); + } +} diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptionsPanelController.java b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptionsPanelController.java new file mode 100644 index 000000000000..d023bce84249 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BCOptionsPanelController.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.options; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import javax.swing.JComponent; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.HelpCtx; +import org.openide.util.Lookup; + +/** + * Controlls the communication. + * + * @author Alexander Reder + * @author Christian Wimmer + */ +final class BCOptionsPanelController extends OptionsPanelController { + + private BCOptionPanel panel; + private boolean changed; + private PropertyChangeSupport prop; + + public BCOptionsPanelController() { + prop = new PropertyChangeSupport(this); + panel = new BCOptionPanel(this); + panel.update(); + } + + public void changed() { + if (!changed) { + changed = true; + prop.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); + } + } + + public void update() { + panel.update(); + changed = false; + } + + public void applyChanges() { + panel.applyChanges(); + changed = false; + } + + public void cancel() { + // Nothing to do. + } + + public boolean isValid() { + return true; + } + + public boolean isChanged() { + return changed; + } + + public HelpCtx getHelpCtx() { + return null; + } + + public JComponent getComponent(Lookup masterLookup) { + return panel; + } + + public void addPropertyChangeListener(PropertyChangeListener l) { + prop.addPropertyChangeListener(l); + } + + public void removePropertyChangeListener(PropertyChangeListener l) { + prop.removePropertyChangeListener(l); + } +} diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/icons/Icons.java b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/icons/Icons.java new file mode 100644 index 000000000000..3831fdf952ff --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/icons/Icons.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.options.icons; + +/** + * + * @author Christian Wimmer + */ +public class Icons { + + private static final String PATH = "at/ssw/visualizer/bc/options/icons/"; + + public static final String BC_OPTION = PATH + "package.gif"; +} diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/BytecodeModel/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..58cd5a764430 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.bc.model +OpenIDE-Module-Layer: at/ssw/visualizer/bc/model/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/bc/model/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/resources/META-INF/services/at.ssw.visualizer.bc.model.BytecodeModel b/visualizer/C1Visualizer/BytecodeModel/src/main/resources/META-INF/services/at.ssw.visualizer.bc.model.BytecodeModel new file mode 100644 index 000000000000..595e968b5da9 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/resources/META-INF/services/at.ssw.visualizer.bc.model.BytecodeModel @@ -0,0 +1 @@ +at.ssw.visualizer.bc.modelimpl.BytecodeModelImpl diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/model/Bundle.properties b/visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/model/Bundle.properties new file mode 100644 index 000000000000..4100074e983f --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/model/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Bytecode Model diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/model/layer.xml b/visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/model/layer.xml new file mode 100644 index 000000000000..7eea191dbefc --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/model/layer.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/options/icons/package.gif b/visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/options/icons/package.gif new file mode 100644 index 0000000000000000000000000000000000000000..ced7996a76b0eb11d19d6c74146359b33ce672cb GIT binary patch literal 412 zcmZ?wbhEHbRA5kGxXJ(mdC_Vm@oJSR8uc04^;rgO`TD)3MpLTIrq)=_ZL(U@X}7xH zas3qcO;cTW%yQo~$7An&uLFyG4leRJu`>A7+NeXz6RvDce0wEp$=PnWtsUz_~(-JL(*-v0mpA8Zx%3B{i*oD2*~3_2jAL4IQ3*u~(=A>*-O!NFz@ zVXc@G8x|gJ7f|+^4hoMN4#=?acW5lw h_?V$jnYn{w!KSCDryFM7JF|21^YaUwyYnOj7ytrwk^2Au literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/BytecodeView/pom.xml b/visualizer/C1Visualizer/BytecodeView/pom.xml new file mode 100644 index 000000000000..18b93e1bac30 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeView/pom.xml @@ -0,0 +1,117 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + BytecodeView + 1.13-SNAPSHOT + nbm + BytecodeView + + UTF-8 + + + + at.ssw.visualizer + VisualizerUI + ${project.version} + + + at.ssw.visualizer + BytecodeEditor + ${project.version} + + + at.ssw.visualizer + BytecodeModel + ${project.version} + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + at.ssw.visualizer + TextEditor + ${project.version} + + + org.netbeans.api + org-netbeans-modules-editor + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-lib + ${netbeans.version} + + + org.netbeans.api + org-openide-loaders + ${netbeans.version} + + + org.netbeans.api + org-openide-nodes + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.BytecodeView + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/BytecodeView/src/main/java/at/ssw/visualizer/bc/view/BCViewTopComponent.java b/visualizer/C1Visualizer/BytecodeView/src/main/java/at/ssw/visualizer/bc/view/BCViewTopComponent.java new file mode 100644 index 000000000000..25b95b5580bd --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeView/src/main/java/at/ssw/visualizer/bc/view/BCViewTopComponent.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.view; + +import at.ssw.visualizer.bc.BCEditorKit; +import at.ssw.visualizer.bc.model.BCTextBuilder; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.texteditor.view.AbstractTextViewTopComponent; +import java.io.Serializable; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; + +/** + * Top component which displays the bytecode for a selected block. + * + * @author Alexander Reder + * @author Christian Wimmer + */ +final class BCViewTopComponent extends AbstractTextViewTopComponent { + + private BCViewTopComponent() { + super(new BCEditorKit()); + setName("Bytecodes"); + setToolTipText("Bytecodes"); + } + + @Override + protected String getContent(ControlFlowGraph cfg, BasicBlock[] blocks) { + BCTextBuilder builder = new BCTextBuilder(); + return builder.buildView(cfg, blocks); + } + + // + private static final String PREFERRED_ID = "BCViewTopComponent"; + private static BCViewTopComponent instance; + + public static synchronized BCViewTopComponent getDefault() { + if (instance == null) { + instance = new BCViewTopComponent(); + } + return instance; + } + + public static synchronized BCViewTopComponent findInstance() { + return (BCViewTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID); + } + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_ALWAYS; + } + + @Override + protected String preferredID() { + return PREFERRED_ID; + } + + @Override + public Object writeReplace() { + return new ResolvableHelper(); + } + + static final class ResolvableHelper implements Serializable { + private static final long serialVersionUID = 1L; + public Object readResolve() { + return BCViewTopComponent.getDefault(); + } + } + // +} diff --git a/visualizer/C1Visualizer/BytecodeView/src/main/java/at/ssw/visualizer/bc/view/ShowBCViewAction.java b/visualizer/C1Visualizer/BytecodeView/src/main/java/at/ssw/visualizer/bc/view/ShowBCViewAction.java new file mode 100644 index 000000000000..a0fc80984833 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeView/src/main/java/at/ssw/visualizer/bc/view/ShowBCViewAction.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.bc.view; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.openide.windows.TopComponent; + +/** + * Action which shows BCView component. + * + * @author Alexander Reder + */ +public class ShowBCViewAction extends AbstractAction { + public ShowBCViewAction() { + super("Bytecodes"); + } + + public void actionPerformed(ActionEvent evt) { + TopComponent win = BCViewTopComponent.findInstance(); + win.open(); + win.requestActive(); + } +} diff --git a/visualizer/C1Visualizer/BytecodeView/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/BytecodeView/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..46ad11a92fab --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeView/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.bc.view +OpenIDE-Module-Layer: at/ssw/visualizer/bc/view/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/bc/view/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/BCViewTopComponentSettings.xml b/visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/BCViewTopComponentSettings.xml new file mode 100644 index 000000000000..85c9b6995abc --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/BCViewTopComponentSettings.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/BCViewTopComponentWstcref.xml b/visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/BCViewTopComponentWstcref.xml new file mode 100644 index 000000000000..7b4d85758d7b --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/BCViewTopComponentWstcref.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/Bundle.properties b/visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/Bundle.properties new file mode 100644 index 000000000000..f1df38cceb82 --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Bytecode View diff --git a/visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/layer.xml b/visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/layer.xml new file mode 100644 index 000000000000..2710385d20bf --- /dev/null +++ b/visualizer/C1Visualizer/BytecodeView/src/main/resources/at/ssw/visualizer/bc/view/layer.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/CompilationModel/pom.xml b/visualizer/C1Visualizer/CompilationModel/pom.xml new file mode 100644 index 000000000000..701fecef313d --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/pom.xml @@ -0,0 +1,99 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + CompilationModel + 1.13-SNAPSHOT + nbm + CompilationModel + + UTF-8 + + + + org.netbeans.api + org-netbeans-api-progress + ${netbeans.version} + + + org.netbeans.api + org-netbeans-api-progress-nb + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.model + at.ssw.visualizer.model.bc + at.ssw.visualizer.model.nc + at.ssw.visualizer.model.cfg + at.ssw.visualizer.model.interval + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.5.0 + + + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/cocor + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/Compilation.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/Compilation.java new file mode 100644 index 000000000000..151cb6eba296 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/Compilation.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model; + +import java.util.Date; + +/** + * + * @author Christian Wimmer + */ +public interface Compilation extends CompilationElement { + public CompilationModel getCompilationModel(); + + public String getMethod(); + + public Date getDate(); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/CompilationElement.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/CompilationElement.java new file mode 100644 index 000000000000..40a28ceb7872 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/CompilationElement.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model; + +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public interface CompilationElement { + public Compilation getCompilation(); + + public CompilationElement getParent(); + + public List getElements(); + + public String getShortName(); + + public String getName(); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/CompilationModel.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/CompilationModel.java new file mode 100644 index 000000000000..c208e3c54ec8 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/CompilationModel.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model; + +import java.util.List; +import javax.swing.event.ChangeListener; + +/** + * + * @author Christian Wimmer + */ +public interface CompilationModel { + public List getCompilations(); + + public String parseInputFile(String fileName); + + public void removeCompilation(Compilation compilation); + + public void clear(); + + public void addChangedListener(ChangeListener listener); + + public void removeChangedListener(ChangeListener listener); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/bc/Bytecodes.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/bc/Bytecodes.java new file mode 100644 index 000000000000..944d8f36830a --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/bc/Bytecodes.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model.bc; + +import at.ssw.visualizer.model.cfg.ControlFlowGraph; + +/** + * This class holds the bytecode of a method and provides severel methods + * accessing the details. + * + * @author Alexander Reder + * @author Christian Wimmer + */ +public interface Bytecodes { + /** + * Back-link to the control flow graph where the bytecodes were loaded from. + */ + public ControlFlowGraph getControlFlowGraph(); + + /** + * Called before the first call of getBytecodes() or getEpilogue(). Can be called multiple times. + */ + public void parseBytecodes(); + + /** + * The bytecodes of the method in the given bytecode range. + * + * @param fromBCI starting BCI (including this bci) + * @param toBCI ending BCI (not including this bci) + * @return string representation of the bytecodes + */ + public String getBytecodes(int fromBCI, int toBCI); + + public String getEpilogue(); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/BasicBlock.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/BasicBlock.java new file mode 100644 index 000000000000..2091934f3f11 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/BasicBlock.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model.cfg; + +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public interface BasicBlock { + public ControlFlowGraph getParent(); + + public String getName(); + + public int getFromBci(); + + public int getToBci(); + + public List getPredecessors(); + + public List getSuccessors(); + + public List getXhandlers(); + + public List getFlags(); + + public BasicBlock getDominator(); + + public int getLoopIndex(); + + public int getLoopDepth(); + + public int getFirstLirId(); + + public int getLastLirId(); + + public double getProbability(); + + public boolean hasState(); + + public List getStates(); + + public boolean hasHir(); + + public List getHirInstructions(); + + public boolean hasLir(); + + public List getLirOperations(); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/ControlFlowGraph.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/ControlFlowGraph.java new file mode 100644 index 000000000000..4eb63b25affd --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/ControlFlowGraph.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model.cfg; + +import at.ssw.visualizer.model.CompilationElement; +import at.ssw.visualizer.model.bc.Bytecodes; +import at.ssw.visualizer.model.nc.NativeMethod; +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public interface ControlFlowGraph extends CompilationElement { + public List getBasicBlocks(); + + public BasicBlock getBasicBlockByName(String name); + + public Bytecodes getBytecodes(); + + public NativeMethod getNativeMethod(); + + public boolean hasState(); + + public boolean hasHir(); + + public boolean hasLir(); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/IRInstruction.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/IRInstruction.java new file mode 100644 index 000000000000..4efff579aa95 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/IRInstruction.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model.cfg; + +import java.util.Collection; + +/** + * + * @author Christian Wimmer + */ +public interface IRInstruction { + public static String HIR_NAME = "tid"; + public static String HIR_TEXT = "instruction"; + public static String HIR_OPERAND = "result"; + + public static String LIR_NUMBER = "nr"; + public static String LIR_TEXT = "instruction"; + + public Collection getNames(); + public String getValue(String name); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/State.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/State.java new file mode 100644 index 000000000000..63ff4772ad8d --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/State.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model.cfg; + +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public interface State { + public String getKind(); + + public int getSize(); + + public String getMethod(); + + public List getEntries(); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/StateEntry.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/StateEntry.java new file mode 100644 index 000000000000..d893177cfbdc --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/cfg/StateEntry.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model.cfg; + +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public interface StateEntry { + public int getIndex(); + + public String getName(); + + public boolean hasPhiOperands(); + + public List getPhiOperands(); + + public String getOperand(); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/ChildInterval.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/ChildInterval.java new file mode 100644 index 000000000000..2aff9f6395c1 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/ChildInterval.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model.interval; + +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public interface ChildInterval { + public Interval getParent(); + + public String getRegNum(); + + public String getType(); + + public String getOperand(); + + public String getSpillState(); + + public ChildInterval getRegisterHint(); + + public List getRanges(); + + public List getUsePositions(); + + public int getFrom(); + + public int getTo(); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/Interval.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/Interval.java new file mode 100644 index 000000000000..e3bebdd9101f --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/Interval.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model.interval; + +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public interface Interval { + public IntervalList getParent(); + + public List getChildren(); + + public String getRegNum(); + + public int getFrom(); + + public int getTo(); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/IntervalList.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/IntervalList.java new file mode 100644 index 000000000000..afc96422b622 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/IntervalList.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model.interval; + +import at.ssw.visualizer.model.CompilationElement; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public interface IntervalList extends CompilationElement { + public List getIntervals(); + + public ControlFlowGraph getControlFlowGraph(); + + public int getNumLIROperations(); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/Range.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/Range.java new file mode 100644 index 000000000000..9e2e60c3c149 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/Range.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model.interval; + +/** + * + * @author Christian Wimmer + */ +public interface Range { + public int getFrom(); + + public int getTo(); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/UsePosition.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/UsePosition.java new file mode 100644 index 000000000000..a36a7a8ed2b6 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/interval/UsePosition.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model.interval; + +/** + * + * @author Christian Wimmer + */ +public interface UsePosition { + public char getKind(); + + public int getPosition(); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/nc/NativeMethod.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/nc/NativeMethod.java new file mode 100644 index 000000000000..75fc0e89bc55 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/model/nc/NativeMethod.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.model.nc; + +import at.ssw.visualizer.model.cfg.ControlFlowGraph; + +/** + * + * @author Alexander Reder + */ +public interface NativeMethod { + + public ControlFlowGraph getControlFlowGraph(); + + public String getMethodText(); + +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/CompilationElementImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/CompilationElementImpl.java new file mode 100644 index 000000000000..57a58a28160d --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/CompilationElementImpl.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl; + +import at.ssw.visualizer.model.Compilation; +import at.ssw.visualizer.model.CompilationElement; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public class CompilationElementImpl implements CompilationElement { + private Compilation compilation; + private CompilationElement parent; + private CompilationElement[] elements; + private String shortName; + private String name; + + public CompilationElementImpl(String shortName, String name) { + this.shortName = shortName; + this.name = name; + this.elements = new CompilationElement[0]; + } + + public Compilation getCompilation() { + return compilation; + } + + public CompilationElement getParent() { + return parent; + } + + public List getElements() { + return Collections.unmodifiableList(Arrays.asList(elements)); + } + + public void setElements(CompilationElementImpl[] elements, Compilation compilation) { + this.elements = elements; + for (CompilationElementImpl ce : elements) { + ce.parent = this; + ce.compilation = compilation; + } + } + + public String getShortName() { + return shortName; + } + + public String getName() { + return name; + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/CompilationImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/CompilationImpl.java new file mode 100644 index 000000000000..25c151a7886e --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/CompilationImpl.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl; + +import at.ssw.visualizer.model.Compilation; +import at.ssw.visualizer.model.CompilationElement; +import at.ssw.visualizer.model.CompilationModel; +import java.util.Date; + +/** + * + * @author Christian Wimmer + */ +public class CompilationImpl extends CompilationElementImpl implements Compilation { + private CompilationModel compilationModel; + private String method; + private Date date; + + public CompilationImpl(String shortName, String name, String method, Date date) { + super(shortName, name); + this.method = method; + this.date = date; + } + + public CompilationModel getCompilationModel() { + return compilationModel; + } + + public void setCompilationModel(CompilationModelImpl compilationModel) { + this.compilationModel = compilationModel; + } + + public String getMethod() { + return method; + } + + public Date getDate() { + return date; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(" Name: ").append(getName()); + result.append("\n Method: ").append(method); + result.append("\n Date: ").append(date); + result.append("\n Elements: ").append(getElements().size()); + result.append("\n"); + for (CompilationElement element : getElements()) { + result.append(element); + } + return result.toString(); + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/CompilationModelImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/CompilationModelImpl.java new file mode 100644 index 000000000000..296f23deab6d --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/CompilationModelImpl.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl; + +import at.ssw.visualizer.model.Compilation; +import at.ssw.visualizer.model.CompilationModel; +import at.ssw.visualizer.parser.CompilationParser; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +/** + * + * @author Christian Wimmer + */ +public class CompilationModelImpl implements CompilationModel { + private List compilations = new ArrayList(); + private List listeners = new ArrayList(); + + private boolean parsing; + + public List getCompilations() { + return Collections.unmodifiableList(compilations); + } + + public String parseInputFile(String fileName) { + parsing = true; + String result; + try { + result = CompilationParser.parseInputFile(fileName, this); + } finally { + parsing = false; + notifyListeners(); + } + return result; + } + + public void addCompilation(CompilationImpl compilation) { + this.compilations.add(compilation); + compilation.setCompilationModel(this); + + if (!parsing || this.compilations.size() % 40 == 0) { + notifyListeners(); + } + } + + public void removeCompilation(Compilation compilation) { + compilations.remove(compilation); + notifyListeners(); + } + + public void clear() { + compilations.clear(); + notifyListeners(); + } + + + public void addChangedListener(ChangeListener listener) { + listeners.add(listener); + } + + public void removeChangedListener(ChangeListener listener) { + listeners.remove(listener); + } + + private void notifyListeners() { + ChangeEvent event = new ChangeEvent(this); + for (ChangeListener listener : listeners) { + listener.stateChanged(event); + } + } + + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("Compilations: "); + result.append(compilations.size()); + result.append("\n"); + for (Compilation compilation : compilations) { + result.append(compilation); + } + return result.toString(); + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/bc/BytecodesImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/bc/BytecodesImpl.java new file mode 100644 index 000000000000..5bf2fae4bf85 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/bc/BytecodesImpl.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl.bc; + +import at.ssw.visualizer.model.bc.Bytecodes; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.modelimpl.cfg.BasicBlockImpl; +import java.util.Arrays; + +/** + * This class holds the bytecode of a method and provides severel methods + * accessing the details. + * + * @author Christian Wimmer + */ +public class BytecodesImpl implements Bytecodes { + private ControlFlowGraph controlFlowGraph; + private String bytecodeString; + + private String[] bytecodes; + private String epilogue; + + + public BytecodesImpl(ControlFlowGraph controlFlowGraph, String bytecodeString) { + this.controlFlowGraph = controlFlowGraph; + this.bytecodeString = bytecodeString; + } + + public void parseBytecodes() { + String[] lines = bytecodeString.split("\n"); + + boolean inPrologue = true; + String[] result = new String[lines.length * 3]; + int lastBci = -1; + int lnr = 0; + for (; lnr < lines.length; lnr++) { + String line = lines[lnr]; + + line = line.trim(); + if (line.startsWith("[")) { + int end = line.indexOf(']'); + if (end != -1) { + line = line.substring(end + 1, line.length()); + } + } + + line = line.trim(); + int space1 = line.indexOf(' '); + if (space1 <= 0) { + if (inPrologue) { + continue; + } else { + break; + } + } + String bciStr = line.substring(0, space1); + if (bciStr.endsWith(":")) { + bciStr = bciStr.substring(0, bciStr.length() - 1); + } + + int bci; + try { + bci = Integer.parseInt(bciStr); + } catch (NumberFormatException ex) { + // Ignore invalid lines. + if (inPrologue) { + continue; + } else { + break; + } + } + + String opcode = line.substring(space1 + 1); + String params = ""; + int space2 = opcode.indexOf(' '); + if (space2 > 0) { + params = opcode.substring(space2 + 1).trim(); + opcode = opcode.substring(0, space2); + } + String tail = ""; + int space3 = params.indexOf('|'); + if (space3 >= 0) { + tail = params.substring(space3); + params = params.substring(0, space3); + } + +// if (!"ldc".equals(opcode) || !params.startsWith("\"")) { +// // Separate packages with "." instead of "/" +// params = params.replace('/', '.'); +// } + + String printLine = bciStr + ":" + " ".substring(Math.min(bciStr.length(), 3)) + + opcode + " ".substring(Math.min(opcode.length(), 13)) + + params + " ".substring(Math.min(params.length(), 8)) + + tail; + + + if (bci >= result.length) { + result = Arrays.copyOf(result, Math.max(bci + 1, result.length * 2)); + } + result[bci] = printLine; + inPrologue = false; + lastBci = Math.max(lastBci, bci); + } + + StringBuilder epilogueBuilder = new StringBuilder(); + for (; lnr < lines.length; lnr++) { + epilogueBuilder.append(lines[lnr]).append("\n"); + } + epilogue = epilogueBuilder.toString(); + bytecodes = Arrays.copyOf(result, lastBci + 1); + + + BasicBlockImpl[] blocks = new BasicBlockImpl[bytecodes.length]; + for (BasicBlock b : controlFlowGraph.getBasicBlocks()) { + if (b instanceof BasicBlockImpl) { + BasicBlockImpl block = (BasicBlockImpl) b; + if (block.getToBci() != -1) { + // Do not override existing values. + return; + } + if (block.getFromBci() >= 0 && block.getFromBci() < blocks.length) { + blocks[block.getFromBci()] = block; + } + } + } + + int curToBci = -1; + for (int i = blocks.length - 1; i >= 0; i--) { + if (bytecodes[i] != null && curToBci == -1) { + curToBci = i; + } + if (blocks[i] != null) { + blocks[i].setToBci(curToBci); + curToBci = -1; + } + } + } + + public ControlFlowGraph getControlFlowGraph() { + return controlFlowGraph; + } + + public String getBytecodes(int fromBCI, int toBCI) { + if (fromBCI < 0) { + return ""; + } + toBCI = Math.min(toBCI, bytecodes.length); + StringBuilder sb = new StringBuilder(); + for (int i = fromBCI; i < toBCI; i++) { + if (bytecodes[i] != null) { + sb.append(bytecodes[i]).append("\n"); + } + } + return sb.toString(); + } + + @Override + public String getEpilogue() { + return epilogue; + } + + @Override + public String toString() { + return "Bytecodes " + getControlFlowGraph().getName(); + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/BasicBlockImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/BasicBlockImpl.java new file mode 100644 index 000000000000..83f6f2c2e4be --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/BasicBlockImpl.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl.cfg; + +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.model.cfg.IRInstruction; +import at.ssw.visualizer.model.cfg.State; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public class BasicBlockImpl implements BasicBlock { + + private ControlFlowGraph parent; + private String name; + private int fromBci; + private int toBci; + private BasicBlock[] predecessors; + private BasicBlock[] successors; + private BasicBlock[] xhandlers; + private String[] flags; + private BasicBlock dominator; + private int loopIndex; + private int loopDepth; + private int firstLirId; + private int lastLirId; + private double probability; + private State[] states; + private IRInstruction[] hirInstructions; + private IRInstruction[] lirOperations; + + public void setValues(String name, int fromBci, int toBci, BasicBlock[] predecessors, BasicBlock[] successors, BasicBlock[] xhandlers, String[] flags, BasicBlock dominator, int loopIndex, int loopDepth, int firstLirId, int lastLirId, double probability, State[] states, IRInstruction[] hirInstructions, IRInstruction[] lirOperations) { + this.name = name; + this.fromBci = fromBci; + this.toBci = toBci; + + this.predecessors = predecessors; + this.successors = successors; + this.xhandlers = xhandlers; + + this.flags = flags; + this.dominator = dominator; + this.loopIndex = loopIndex; + this.loopDepth = loopDepth; + this.firstLirId = firstLirId; + this.lastLirId = lastLirId; + this.probability = probability; + + this.states = states; + this.hirInstructions = hirInstructions; + this.lirOperations = lirOperations; + } + + public ControlFlowGraph getParent() { + return parent; + } + + protected void setParent(ControlFlowGraphImpl parent) { + this.parent = parent; + } + + public String getName() { + return name; + } + + public int getFromBci() { + return fromBci; + } + + public int getToBci() { + return toBci; + } + + public void setToBci(int toBci) { + this.toBci = toBci; + } + + public List getPredecessors() { + return Collections.unmodifiableList(Arrays.asList(predecessors)); + } + + public List getSuccessors() { + return Collections.unmodifiableList(Arrays.asList(successors)); + } + + public List getXhandlers() { + return Collections.unmodifiableList(Arrays.asList(xhandlers)); + } + + public List getFlags() { + return Collections.unmodifiableList(Arrays.asList(flags)); + } + + public BasicBlock getDominator() { + return dominator; + } + + public int getLoopIndex() { + return loopIndex; + } + + public int getLoopDepth() { + return loopDepth; + } + + public int getFirstLirId() { + return firstLirId; + } + + public int getLastLirId() { + return lastLirId; + } + + public double getProbability() { + return probability; + } + + public boolean hasState() { + return states != null; + } + + public List getStates() { + return Collections.unmodifiableList(Arrays.asList(states)); + } + + public boolean hasHir() { + return hirInstructions != null; + } + + public List getHirInstructions() { + return Collections.unmodifiableList(Arrays.asList(hirInstructions)); + } + + public boolean hasLir() { + return lirOperations != null; + } + + public List getLirOperations() { + return Collections.unmodifiableList(Arrays.asList(lirOperations)); + } + + @Override + public String toString() { + return name; + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/ControlFlowGraphImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/ControlFlowGraphImpl.java new file mode 100644 index 000000000000..c87d9974a194 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/ControlFlowGraphImpl.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl.cfg; + +import at.ssw.visualizer.model.bc.Bytecodes; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.model.nc.NativeMethod; +import at.ssw.visualizer.modelimpl.CompilationElementImpl; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + * @author Christian Wimmer + */ +public class ControlFlowGraphImpl extends CompilationElementImpl implements ControlFlowGraph { + private BasicBlock[] basicBlocks; + private Map blockNames; + private Bytecodes bytecodes; + private NativeMethod nativeMethod; + private boolean hasState; + private boolean hasHir; + private boolean hasLir; + + public ControlFlowGraphImpl(String shortName, String name, BasicBlockImpl[] basicBlocks) { + super(shortName, name); + this.basicBlocks = basicBlocks; + + blockNames = new HashMap(basicBlocks.length); + nativeMethod = null; + for (BasicBlockImpl block : basicBlocks) { + block.setParent(this); + blockNames.put(block.getName(), block); + hasState |= block.hasState(); + hasHir |= block.hasHir(); + hasLir |= block.hasLir(); + } + } + + public List getBasicBlocks() { + return Collections.unmodifiableList(Arrays.asList(basicBlocks)); + } + + public BasicBlock getBasicBlockByName(String name) { + return blockNames.get(name); + } + + public Bytecodes getBytecodes() { + return bytecodes; + } + + public void setBytecodes(Bytecodes bytecodes) { + this.bytecodes = bytecodes; + } + + public NativeMethod getNativeMethod() { + return nativeMethod; + } + + public void setNativeMethod(NativeMethod nativeMethod) { + this.nativeMethod = nativeMethod; + } + + public boolean hasState() { + return hasState; + } + + public boolean hasHir() { + return hasHir; + } + + public boolean hasLir() { + return hasLir; + } + + @Override + public String toString() { + return " CFG \"" + getName() + "\": " + basicBlocks.length + " blocks\n"; + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/IRInstructionImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/IRInstructionImpl.java new file mode 100644 index 000000000000..31927e3b9413 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/IRInstructionImpl.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl.cfg; + +import at.ssw.visualizer.model.cfg.IRInstruction; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map.Entry; + +public class IRInstructionImpl implements IRInstruction { + + private final String[] keys; + private final String[] values; + + public IRInstructionImpl(LinkedHashMap data) { + String[] skeys = new String[data.size()]; + String[] svalues = new String[data.size()]; + int index = 0; + for (Entry e : data.entrySet()) { + skeys[index] = e.getKey().intern(); + svalues[index++] = e.getValue().intern(); + } + if (Arrays.equals(skeys, twoKeys)) { + keys = twoKeys; + } else { + keys = skeys; + } + values = svalues; + } + + public IRInstructionImpl(String pinned, int bci, int useCount, String name, String text, String operand) { + final String p = checkIntern(pinned); + final String b = Integer.toString(bci).intern(); + final String u = Integer.toString(useCount).intern(); + final String n = checkIntern(name); + final String i = checkIntern(text); + if (operand != null) { + keys = withOperandKeys; + values = new String[]{p, b, u, n, checkIntern(operand), i}; + } else { + keys = noOperandKeys; + values = new String[]{p, b, u, n, i}; + } + } + + public IRInstructionImpl(int number, String text) { + keys = twoKeys; + values = new String[]{Integer.toString(number).intern(), checkIntern(text)}; + } + + static final String[] twoKeys = new String[]{LIR_NUMBER, LIR_TEXT}; + static final String[] noOperandKeys = new String[]{"p", "bci", "use", HIR_NAME, HIR_TEXT}; + static final String[] withOperandKeys = new String[]{"p", "bci", "use", HIR_NAME, HIR_OPERAND, HIR_TEXT}; + + public Collection getNames() { + return Collections.unmodifiableList(Arrays.asList(keys)); + } + + public String getValue(String name) { + for (int i = 0; i < keys.length; i++) { + if (keys[i].equals(name)) { + return values[i]; + } + } + return null; + } + + private String checkIntern(String s) { + if (s != s.intern()) { + throw new InternalError("non-interned String passed to IRInstructionImpl constructor"); + } + return s; + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/StateEntryImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/StateEntryImpl.java new file mode 100644 index 000000000000..2446fa1e4b95 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/StateEntryImpl.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl.cfg; + +import at.ssw.visualizer.model.cfg.StateEntry; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public class StateEntryImpl implements StateEntry { + private int index; + private String name; + private String[] phiOperands; + private String operand; + + public StateEntryImpl(int index, String name, String[] phiOperands, String operand) { + this.index = index; + this.name = name; + this.phiOperands = phiOperands; + this.operand = operand; + } + + public int getIndex() { + return index; + } + + public String getName() { + return name; + } + + public boolean hasPhiOperands() { + return phiOperands != null; + } + + public List getPhiOperands() { + return Collections.unmodifiableList(Arrays.asList(phiOperands)); + } + + public String getOperand() { + return operand; + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/StateImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/StateImpl.java new file mode 100644 index 000000000000..64b328f4f5f7 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/cfg/StateImpl.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl.cfg; + +import at.ssw.visualizer.model.cfg.State; +import at.ssw.visualizer.model.cfg.StateEntry; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public class StateImpl implements State { + private String kind; + private int size; + private String method; + private StateEntry[] entries; + + public StateImpl(String kind, int size, String method, StateEntryImpl[] entries) { + this.kind = kind; + this.size = size; + this.method = method; + this.entries = entries; + } + + public String getKind() { + return kind; + } + + public int getSize() { + return size; + } + + public String getMethod() { + return method; + } + + public List getEntries() { + return Collections.unmodifiableList(Arrays.asList(entries)); + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/ChildIntervalImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/ChildIntervalImpl.java new file mode 100644 index 000000000000..4c2f40459b79 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/ChildIntervalImpl.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl.interval; + +import at.ssw.visualizer.model.interval.ChildInterval; +import at.ssw.visualizer.model.interval.Interval; +import at.ssw.visualizer.model.interval.Range; +import at.ssw.visualizer.model.interval.UsePosition; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public class ChildIntervalImpl implements ChildInterval, Comparable { + private Interval parent; + private String regNum; + private String type; + private String operand; + private String spillState; + private ChildInterval registerHint; + private Range[] ranges; + private UsePosition[] usePositions; + + public void setValues(String regNum, String type, String operand, String spillState, ChildInterval registerHint, Range[] ranges, UsePosition[] usePositions) { + this.regNum = regNum; + this.type = type; + this.operand = operand; + this.spillState = spillState; + this.registerHint = registerHint; + this.ranges = ranges; + this.usePositions = usePositions; + } + + public Interval getParent() { + return parent; + } + + protected void setParent(IntervalImpl parent) { + this.parent = parent; + } + + public String getRegNum() { + return regNum; + } + + public String getType() { + return type; + } + + public String getOperand() { + return operand; + } + + public String getSpillState() { + return spillState; + } + + public ChildInterval getRegisterHint() { + return registerHint; + } + + public List getRanges() { + return Collections.unmodifiableList(Arrays.asList(ranges)); + } + + public List getUsePositions() { + return Collections.unmodifiableList(Arrays.asList(usePositions)); + } + + + public int getFrom() { + return ranges[0].getFrom(); + } + + public int getTo() { + return ranges[ranges.length - 1].getTo(); + } + + + public int compareTo(ChildIntervalImpl other) { + return getFrom() - other.getFrom(); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(regNum); + result.append(": "); + result.append(getType()); + result.append(", "); + result.append(getOperand()); + result.append(", "); + if (registerHint != null) { + result.append(registerHint.getRegNum()); + } else { + result.append("null"); + } + + result.append(" "); + for (int i = 0; i < ranges.length; i++) { + if (i > 0) { + result.append(", "); + } + result.append(ranges[i]); + } + + result.append(" "); + for (int i = 0; i < usePositions.length; i++) { + if (i > 0) { + result.append(", "); + } + result.append(usePositions[i]); + } + + return result.toString(); + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/IntervalImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/IntervalImpl.java new file mode 100644 index 000000000000..fc7c3d2d4a1b --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/IntervalImpl.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl.interval; + +import at.ssw.visualizer.model.interval.ChildInterval; +import at.ssw.visualizer.model.interval.Interval; +import at.ssw.visualizer.model.interval.IntervalList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public class IntervalImpl implements Interval { + private IntervalList parent; + private ChildInterval[] children; + + public IntervalImpl(ChildIntervalImpl[] children) { + this.children = children; + for (ChildIntervalImpl child : children) { + child.setParent(this); + } + } + + public IntervalList getParent() { + return parent; + } + + protected void setParent(IntervalListImpl parent) { + this.parent = parent; + } + + public List getChildren() { + return Collections.unmodifiableList(Arrays.asList(children)); + } + + public String getRegNum() { + return children[0].getRegNum(); + } + + public int getFrom() { + return children[0].getFrom(); + } + + public int getTo() { + return children[children.length - 1].getTo(); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < children.length; i++) { + if (i > 0) { + result.append("\n "); + } + result.append(children[i]); + } + return result.toString(); + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/IntervalListImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/IntervalListImpl.java new file mode 100644 index 000000000000..819294eba126 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/IntervalListImpl.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl.interval; + +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.model.interval.Interval; +import at.ssw.visualizer.model.interval.IntervalList; +import at.ssw.visualizer.modelimpl.CompilationElementImpl; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * + * @author Christian Wimmer + */ +public class IntervalListImpl extends CompilationElementImpl implements IntervalList { + private Interval[] intervals; + private ControlFlowGraph controlFlowGraph; + private int numLIROperations; + + public IntervalListImpl(String shortName, String name, IntervalImpl[] intervals, ControlFlowGraph controlFlowGraph) { + super(shortName, name); + this.intervals = intervals; + this.controlFlowGraph = controlFlowGraph; + + for (IntervalImpl interval : intervals) { + interval.setParent(this); + numLIROperations = Math.max(numLIROperations, interval.getTo()); + } + for (BasicBlock basicBlock : controlFlowGraph.getBasicBlocks()) { + numLIROperations = Math.max(numLIROperations, basicBlock.getLastLirId() + 2); + } + } + + + public List getIntervals() { + return Collections.unmodifiableList(Arrays.asList(intervals)); + } + + public ControlFlowGraph getControlFlowGraph() { + return controlFlowGraph; + } + + public int getNumLIROperations() { + return numLIROperations; + } + + + @Override + public String toString() { + return " Intervals \"" + getName() + "\": " + intervals.length + " intervals\n"; + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/RangeImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/RangeImpl.java new file mode 100644 index 000000000000..9d39e551ae35 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/RangeImpl.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl.interval; + +import at.ssw.visualizer.model.interval.Range; + +/** + * + * @author Christian Wimmer + */ +public class RangeImpl implements Range, Comparable { + private int from; + private int to; + + public RangeImpl(int from, int to) { + this.from = from; + this.to = to; + } + + + public int getFrom() { + return from; + } + + public int getTo() { + return to; + } + + + public int compareTo(RangeImpl other) { + return getFrom() - other.getFrom(); + } + + @Override + public String toString() { + return "[" + from + ", " + to + "]"; + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/UsePositionImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/UsePositionImpl.java new file mode 100644 index 000000000000..2e9a1e6719a3 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/interval/UsePositionImpl.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl.interval; + +import at.ssw.visualizer.model.interval.UsePosition; + +/** + * + * @author Christian Wimmer + */ +public class UsePositionImpl implements UsePosition, Comparable { + private int position; + private char kind; + + public UsePositionImpl(int position, char kind) { + this.position = position; + this.kind = kind; + } + + + public char getKind() { + return kind; + } + + public int getPosition() { + return position; + } + + + public int compareTo(UsePositionImpl other) { + return getPosition() - other.getPosition(); + } + + @Override + public String toString() { + return position + "(" + kind + ")"; + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/nc/NativeMethodImpl.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/nc/NativeMethodImpl.java new file mode 100644 index 000000000000..523fdbbb7bd1 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/modelimpl/nc/NativeMethodImpl.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.modelimpl.nc; + +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.model.nc.NativeMethod; + +/** + * + * @author Alexander Reder + */ +public class NativeMethodImpl implements NativeMethod { + + private ControlFlowGraph controlFlowGraph; + private String methodText; + + public NativeMethodImpl(ControlFlowGraph controlFlowGraph, String methodText) { + this.controlFlowGraph = controlFlowGraph; + this.methodText = methodText; + } + + public ControlFlowGraph getControlFlowGraph() { + return controlFlowGraph; + } + + public String getMethodText() { + return methodText; + } + +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/BBHelper.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/BBHelper.java new file mode 100644 index 000000000000..955e804cb12d --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/BBHelper.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.parser; + +import java.util.ArrayList; +import java.util.List; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.IRInstruction; +import at.ssw.visualizer.model.cfg.State; +import at.ssw.visualizer.modelimpl.cfg.BasicBlockImpl; + +/** + * + * @author Christian Wimmer + */ +public class BBHelper { + + protected String name; + protected int fromBci; + protected int toBci; + protected String[] predecessors; + protected String[] successors; + protected String[] xhandlers; + protected String[] flags; + protected String dominator; + protected int loopIndex; + protected int loopDepth; + protected int firstLirId; + protected int lastLirId; + protected double probability = Double.NaN; + protected List states = new ArrayList(); + protected List hirInstructions = new ArrayList(); + protected List lirOperations = new ArrayList(); + protected BasicBlockImpl basicBlock = new BasicBlockImpl(); + protected List defPredecessorsList = new ArrayList(); + protected List calcPredecessorsList = new ArrayList(); + protected List successorsList = new ArrayList(); + protected List xhandlersList = new ArrayList(); +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CFGHelper.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CFGHelper.java new file mode 100644 index 000000000000..c6ed49ec5d9b --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CFGHelper.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.parser; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.IRInstruction; +import at.ssw.visualizer.model.cfg.State; +import at.ssw.visualizer.modelimpl.cfg.BasicBlockImpl; +import at.ssw.visualizer.modelimpl.cfg.ControlFlowGraphImpl; + +/** + * + * @author Christian Wimmer + */ +public class CFGHelper { + + protected String shortName; + protected String name; + protected int id; + protected int parentId; + private Map helpers = new HashMap(); + private List helpersList = new ArrayList(); + protected List elements = new ArrayList(); + protected ControlFlowGraphImpl resolved; + + public void add(BBHelper helper) { + helpers.put(helper.name, helper); + helpersList.add(helper); + } + + public ControlFlowGraphImpl resolve(CompilationHelper lastComp, Parser parser) { + for (BBHelper helper : helpersList) { + for (String predecessorName : helper.predecessors) { + BBHelper predecessor = helpers.get(predecessorName); + if (predecessor != null) { + helper.defPredecessorsList.add(predecessor.basicBlock); + } else { + // ignore + // parser.SemErr("Undefined predecessor: " + predecessorName); + } + } + + for (String successorName : helper.successors) { + BBHelper successor = helpers.get(successorName); + if (successor != null) { + helper.successorsList.add(successor.basicBlock); + successor.calcPredecessorsList.add(helper.basicBlock); + } else { + // ignore + // parser.SemErr("Undefined successor: " + successorName); + } + } + + for (String xhandlerName : helper.xhandlers) { + BBHelper xhandler = helpers.get(xhandlerName); + if (xhandler != null) { + helper.xhandlersList.add(xhandler.basicBlock); + xhandler.calcPredecessorsList.add(helper.basicBlock); + } else { + // ignore + // parser.SemErr("Undefined xhandler: " + xhandlerName); + } + } + } + + BasicBlockImpl[] basicBlocks = new BasicBlockImpl[helpersList.size()]; + int idx = 0; + for (BBHelper helper : helpersList) { + basicBlocks[idx++] = helper.basicBlock; + + List predecessorsList; + if (helper.defPredecessorsList.size() > 0) { + // check if defined and calculated predecessors are equal + if (helper.defPredecessorsList.size() != helper.calcPredecessorsList.size()) { + parser.SemErr("Defined and calculated predecessors size different: " + helper.name); + } else { + for (BasicBlock block : helper.defPredecessorsList) { + if (!helper.calcPredecessorsList.remove(block)) { + parser.SemErr("Defined and calculated predecessors not matching: " + helper.name); + } + } + if (helper.calcPredecessorsList.size() > 0) { + // should never come here, but just checking... + parser.SemErr("Defined and calculated predecessors not matching: " + helper.name); + } + } + predecessorsList = helper.defPredecessorsList; + } else { + predecessorsList = helper.calcPredecessorsList; + } + + BasicBlock[] predecessors = predecessorsList.toArray(new BasicBlock[predecessorsList.size()]); + BasicBlock[] successors = helper.successorsList.toArray(new BasicBlock[helper.successorsList.size()]); + BasicBlock[] xhandlers = helper.xhandlersList.toArray(new BasicBlock[helper.xhandlersList.size()]); + + BasicBlock dominator = null; + if (helpers.get(helper.dominator) != null) { + dominator = helpers.get(helper.dominator).basicBlock; + } + + State[] states = null; + if (helper.states.size() > 0) { + states = helper.states.toArray(new State[helper.states.size()]); + } + + IRInstruction[] hirInstructions = null; + if (helper.hirInstructions.size() > 0) { + hirInstructions = helper.hirInstructions.toArray(new IRInstruction[helper.hirInstructions.size()]); + } + IRInstruction[] lirOperations = null; + if (helper.lirOperations.size() > 0) { + lirOperations = helper.lirOperations.toArray(new IRInstruction[helper.lirOperations.size()]); + + if (helper.firstLirId == 0) { + try { + helper.firstLirId = Integer.parseInt(lirOperations[0].getValue(IRInstruction.LIR_NUMBER)); + } catch (NumberFormatException ex) { + // Silently ignore invalid numbers. + } + } + if (helper.lastLirId == 0) { + try { + helper.lastLirId = Integer.parseInt(lirOperations[lirOperations.length - 1].getValue(IRInstruction.LIR_NUMBER)); + } catch (NumberFormatException ex) { + // Silently ignore invalid numbers. + } + } + } + + helper.basicBlock.setValues(helper.name, helper.fromBci, helper.toBci, predecessors, successors, xhandlers, helper.flags, dominator, helper.loopIndex, helper.loopDepth, helper.firstLirId, helper.lastLirId, helper.probability, states, hirInstructions, lirOperations); + } + + resolved = new ControlFlowGraphImpl(shortName, name, basicBlocks); + + if (parentId == 0) { + lastComp.elements.add(resolved); + } else { + CFGHelper parent = lastComp.idToCFG.get(parentId); + if (parent != null) { + parent.elements.add(resolved); + } else { + parser.SemErr("Undefined compilation id: " + parentId); + } + } + if (id != 0) { + lastComp.idToCFG.put(id, this); + } + + return resolved; + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CompilationHelper.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CompilationHelper.java new file mode 100644 index 000000000000..d9ad0ee362ab --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CompilationHelper.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.parser; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import at.ssw.visualizer.modelimpl.CompilationElementImpl; +import at.ssw.visualizer.modelimpl.CompilationImpl; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author Christian Wimmer + */ +public class CompilationHelper { + protected String shortName; + protected String name; + protected String method; + protected Date date; + protected List elements = new ArrayList(); + + protected Map idToCFG = new HashMap(); + + protected CompilationImpl resolve() { + CompilationImpl compilation = new CompilationImpl(shortName, name, method, date); + + compilation.setElements(elements.toArray(new CompilationElementImpl[elements.size()]), compilation); + for (CFGHelper cfg : idToCFG.values()) { + cfg.resolved.setElements(cfg.elements.toArray(new CompilationElementImpl[cfg.elements.size()]), compilation); + } + + return compilation; + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CompilationParser.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CompilationParser.java new file mode 100644 index 000000000000..1b698a886de6 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CompilationParser.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.parser; + +import at.ssw.visualizer.modelimpl.CompilationModelImpl; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; +import org.openide.util.Cancellable; + +/** + * + * @author Christian Wimmer + */ +public class CompilationParser { + public static String parseInputFile(String fileName, CompilationModelImpl compilationData) { + ProgressHandle progressHandle = ProgressHandleFactory.createHandle("Parsing input file \"" + fileName + "\"", new CancelParsing()); + Scanner scanner = null; + try { + scanner = new Scanner(fileName, progressHandle); + Parser parser = new Parser(scanner); + parser.setCompilationModel(compilationData); + parser.Parse(); + + if (parser.hasErrors()) { + return parser.getErrors(); + } else { + return null; + } + } catch (UserCanceledError ex) { + // user canceled parsing, so report no error + return null; + } catch (Error ex) { + return ex.getMessage(); + } catch (Throwable ex) { + // catch everything else that might happen + return ex.getClass().getName() + (ex.getMessage() != null ? ": " + ex.getMessage() : ""); + } finally { + progressHandle.finish(); + } + } + + private static class CancelParsing implements Cancellable { + public boolean cancel() { + throw new UserCanceledError(); + } + } + + private static class UserCanceledError extends Error { + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CompilerOutput.atg b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CompilerOutput.atg new file mode 100644 index 000000000000..3929bbd11eae --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/CompilerOutput.atg @@ -0,0 +1,357 @@ +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Date; +import java.util.List; + +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.modelimpl.CompilationImpl; +import at.ssw.visualizer.modelimpl.CompilationModelImpl; +import at.ssw.visualizer.modelimpl.cfg.ControlFlowGraphImpl; +import at.ssw.visualizer.modelimpl.cfg.IRInstructionImpl; +import at.ssw.visualizer.modelimpl.cfg.StateImpl; +import at.ssw.visualizer.modelimpl.cfg.StateEntryImpl; +import at.ssw.visualizer.modelimpl.interval.IntervalListImpl; +import at.ssw.visualizer.modelimpl.interval.RangeImpl; +import at.ssw.visualizer.modelimpl.interval.UsePositionImpl; +import at.ssw.visualizer.modelimpl.nc.NativeMethodImpl; +import at.ssw.visualizer.modelimpl.bc.BytecodesImpl; + +COMPILER InputFile + +private CompilationModelImpl compilationModel; + +public void setCompilationModel(CompilationModelImpl compilationModel) { + this.compilationModel = compilationModel; +} + +private String simpleName(String className) { + int index = className.lastIndexOf('.'); + if (index < 0) { + return className; + } + return className.substring(index + 1); +} + +private String shortName(String name) { + name = longName(name); + String params = ""; + + int openParam = name.indexOf('('); + if (openParam >= 0) { + int closeParam = name.indexOf(')', openParam); + if (closeParam >= 0) { + String[] parts = name.substring(openParam + 1, closeParam).split(", *"); + for (int i = 0; i < parts.length; i++) { + if (!params.isEmpty()) { + params += ","; + } + params += simpleName(parts[i]); + } + params = "(" + params + ")"; + } + name = name.substring(0, openParam); + } + + int methodPoint = name.lastIndexOf("."); + if (methodPoint < 0) { + return name + params; + } + int classPoint = name.lastIndexOf(".", methodPoint - 1); + if (classPoint < 0) { + return name + params; + } + return name.substring(classPoint + 1) + params; +} + +private String longName(String name) { + return name.replace("::", "."); +} + +CHARACTERS + identCh = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-|:*". + cr = '\r'. + lf = '\n'. + +TOKENS + ident = identCh { identCh }. + +IGNORE cr + lf + +/*-------------------------------------------------------------------------*/ + +PRODUCTIONS + + +InputFile = (. CompilationHelper lastComp = null; + ControlFlowGraphImpl lastCFG = null; + IntervalListImpl lastIL = null; .) +{ + Compilation (. if (lastComp != null) compilationModel.addCompilation(lastComp.resolve()); + lastComp = curComp; .) +| + CFG +| + IntervalList (. lastComp.elements.add(lastIL); .) +| + NativeMethod +| + Bytecodes +} (. if (lastComp != null) compilationModel.addCompilation(lastComp.resolve()); .) + +. + + +Compilation = (. helper = new CompilationHelper(); .) +"begin_compilation" + "name" StringValue (. helper.name = longName(name); helper.shortName = shortName(name); .) + "method" StringValue (. helper.method = longName(method); .) + "date" DateValue +"end_compilation". + + + +CFG = (. CFGHelper helper = new CFGHelper(); .) +"begin_cfg" + "name" StringValue (. helper.name = longName(name); helper.shortName = shortName(name); .) + [ + "id" IntegerValue + "caller_id" IntegerValue + ] + { + BasicBlock (. helper.add(basicBlock); .) + } (. res = helper.resolve(lastComp, this); .) +"end_cfg". + + +BasicBlock = (. helper = new BBHelper(); .) +"begin_block" + "name" StringValue + "from_bci" IntegerValue + "to_bci" IntegerValue + + "predecessors" StringList + "successors" StringList + "xhandlers" StringList + "flags" StringList + + [ "dominator" StringValue ] + [ "loop_index" IntegerValue ] + [ "loop_depth" IntegerValue ] + [ "first_lir_id" IntegerValue ] + [ "last_lir_id" IntegerValue ] + [ "probability" DoubleValue ] + + [ StateList ] + [ HIR ] + [ LIR ] + { IR } +"end_block". + + +StateList = +"begin_states" + { + "begin_stack" + State (. helper.states.add(state); .) + "end_stack" + | + "begin_locks" + State (. helper.states.add(state); .) + "end_locks" + | + "begin_locals" + State (. helper.states.add(state); .) + "end_locals" + } +"end_states". + + +State = (. String method = ""; ArrayList entries = new ArrayList(); .) + "size" IntegerValue + [ "method" StringValue ] + { + StateEntry (. entries.add(entry); .) + } (. res = new StateImpl(kind, size, longName(method), entries.toArray(new StateEntryImpl[entries.size()])); .) +. + +StateEntry = (. String[] operands = null; String operand = null; .) + IntegerValue + HIRName + [ + "[" (. ArrayList operandsList = new ArrayList(); .) + { + HIRName (. operandsList.add(opd); .) + } + "]" (. operands = operandsList.toArray(new String[operandsList.size()]); .) + ] + [ + StringValue + ] (. res = new StateEntryImpl(index, name, operands, operand); .) +. + + +HIR = +"begin_HIR" + { + HIRInstruction (. helper.hirInstructions.add(ins); .) + } +"end_HIR". + + +HIRInstruction = (. String pinned = ""; String operand = null; .) + [ + "." (. pinned = "."; .) + ] + IntegerValue + IntegerValue + [ + StringValue + ] + HIRName + FreeValue (. res = new IRInstructionImpl(pinned, bci, useCount, name, text, operand); .) +. + +HIRName = + IdentValue (. if (res.charAt(0) >= '0' && res.charAt(0) <= '9') { res = "v" + res; res = res.intern(); } .) +. + + +LIR = +"begin_LIR" + { + LIROperation (. helper.lirOperations.add(op); .) + } +"end_LIR". + + +LIROperation = + IntegerValue + FreeValue (. res = new IRInstructionImpl(number, text); .) +. + +IR = +"begin_IR" +( + "HIR" + { + IRInstruction (. helper.hirInstructions.add(op); .) + } +| + "LIR" + { + IRInstruction (. helper.lirOperations.add(op); .) + } +) +"end_IR". + +IRInstruction = (. LinkedHashMap data = new LinkedHashMap(); .) + { + IdentValue + FreeValue (. data.put(name.intern(), value.intern()); .) + } + "<|@" (. res = new IRInstructionImpl(data); .) +. + + + +IntervalList = + (. IntervalListHelper helper = new IntervalListHelper(); IntervalHelper interval; + if (controlFlowGraph == null) SemErr("must have CFG before intervals"); .) +"begin_intervals" + "name" StringValue (. helper.name = longName(name); helper.shortName = shortName(name); .) + { + Interval (. helper.add(interval); .) + } (. res = helper.resolve(this, controlFlowGraph); .) +"end_intervals". + + +Interval = (. helper = new IntervalHelper(); RangeImpl range; UsePositionImpl usePosition; .) + IdentValue + IdentValue + [ StringValue ] + IdentValue + IdentValue + + Range (. helper.ranges.add(range); .) + { + Range (. helper.ranges.add(range); .) + } + { + UsePosition (. helper.usePositions.add(usePosition); .) + } + + StringValue +. + + +Range = + "[" IntegerValue + "," IntegerValue "[" (. res = new RangeImpl(from, to); .) +. + + +UsePosition = + IntegerValue + IdentValue (. res = new UsePositionImpl(position, kindStr.charAt(0)); .) +. + + +NativeMethod = +"begin_nmethod" + NoTrimFreeValue (. cfg.setNativeMethod(new NativeMethodImpl(cfg, res)); .) +"end_nmethod" +. + + +Bytecodes = +"begin_bytecodes" + FreeValue (. cfg.setBytecodes(new BytecodesImpl(cfg, res)); .) +"end_bytecodes" +. + + +StringList = (. ArrayList list = new ArrayList(); String item; .) + { + StringValue (. list.add(item); .) + } (. res = list.toArray(new String[list.size()]); .) +. + + +StringValue = + "\"" (. int beg = la.pos; .) + { ANY } (. res = scanner.buffer.GetString(beg, la.pos).trim().intern(); .) + "\"" +. + + +IdentValue = + ident (. res = t.val.trim().intern(); .) +. + + +IntegerValue = (. res = 0; .) + ident (. try { res = Integer.parseInt(t.val); } catch (NumberFormatException ex) { SemErr(t.val); } .) +. + + +DoubleValue = (. res = Double.NaN; .) + ident (. try { res = Double.longBitsToDouble(Long.parseLong(t.val)); } catch (NumberFormatException ex) { SemErr(t.val); } .) +. + + +DateValue = (. res = null; .) + ident (. try { res = new Date(Long.parseLong(t.val)); } catch (NumberFormatException ex) { SemErr(t.val); } .) +. + + +FreeValue = (. int beg = la.pos; .) + { ANY } (. res = scanner.buffer.GetString(beg, la.pos).trim(); if (res.indexOf('\r') != -1) { res = res.replace("\r\n", "\n"); } res = res.intern(); .) + "<|@" +. + +NoTrimFreeValue = (. int beg = la.pos; .) + { ANY } (. res = scanner.buffer.GetString(beg, la.pos); if (res.indexOf('\r') != -1) { res = res.replace("\r\n", "\n"); } res = res.intern(); .) + "<|@" +. + +END InputFile. diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/IntervalHelper.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/IntervalHelper.java new file mode 100644 index 000000000000..77402a43e8f5 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/IntervalHelper.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.parser; + +import java.util.SortedSet; +import java.util.TreeSet; +import at.ssw.visualizer.model.interval.Range; +import at.ssw.visualizer.model.interval.UsePosition; +import at.ssw.visualizer.modelimpl.interval.ChildIntervalImpl; + +/** + * + * @author Christian Wimmer + */ +public class IntervalHelper implements Comparable { + protected String regNum; + protected String type; + protected String operand = ""; // avoid null values if not defined + protected String splitParent; + protected String spillState; + protected String registerHint; + + protected SortedSet ranges = new TreeSet(); + protected SortedSet usePositions = new TreeSet(); + + protected ChildIntervalImpl childInterval = new ChildIntervalImpl(); + protected SortedSet splitChildren = new TreeSet(); + + public int compareTo(IntervalHelper other) { + return ranges.first().getFrom() - other.ranges.first().getFrom(); + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/IntervalListHelper.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/IntervalListHelper.java new file mode 100644 index 000000000000..118d1478856e --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/IntervalListHelper.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.parser; + +import java.util.Collection; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.model.interval.ChildInterval; +import at.ssw.visualizer.model.interval.Range; +import at.ssw.visualizer.model.interval.UsePosition; +import at.ssw.visualizer.modelimpl.interval.ChildIntervalImpl; +import at.ssw.visualizer.modelimpl.interval.IntervalImpl; +import at.ssw.visualizer.modelimpl.interval.IntervalListImpl; +import java.util.ArrayList; +import java.util.LinkedHashMap; + +/** + * + * @author Christian Wimmer + */ +public class IntervalListHelper { + protected String shortName; + protected String name; + protected LinkedHashMap helpers = new LinkedHashMap(); + + public void add(IntervalHelper helper) { + helpers.put(helper.regNum, helper); + } + + + public IntervalListImpl resolve(Parser parser, ControlFlowGraph controlFlowGraph) { + for (IntervalHelper helper : helpers.values()) { + ChildInterval registerHint = null; + if (helpers.containsKey(helper.registerHint)) { + registerHint = helpers.get(helper.registerHint).childInterval; + } + Range[] ranges = helper.ranges.toArray(new Range[helper.ranges.size()]); + UsePosition[] usePositions = helper.usePositions.toArray(new UsePosition[helper.usePositions.size()]); + + helper.childInterval.setValues(helper.regNum, helper.type, helper.operand, helper.spillState, registerHint, ranges, usePositions); + + + IntervalHelper parent = helpers.get(helper.splitParent); + if (parent != null) { + parent.splitChildren.add(helper.childInterval); + } else { + parser.SemErr("Unknown split parent: " + helper.splitParent); + } + } + + Collection intervals = new ArrayList(); + for (IntervalHelper helper : helpers.values()) { + if (helper.splitChildren.size() > 0) { + ChildIntervalImpl[] children = helper.splitChildren.toArray(new ChildIntervalImpl[helper.splitChildren.size()]); + intervals.add(new IntervalImpl(children)); + } + } + + return new IntervalListImpl(shortName, name, intervals.toArray(new IntervalImpl[intervals.size()]), controlFlowGraph); + } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Parser.frame b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Parser.frame new file mode 100644 index 000000000000..638d40c26e97 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Parser.frame @@ -0,0 +1,179 @@ +/*------------------------------------------------------------------------- +Compiler Generator Coco/R, +Copyright (c) 1990, 2004 Hanspeter Moessenboeck, University of Linz +extended by M. Loeberbauer & A. Woess, Univ. of Linz +ported from C# to Java by Wolfgang Ahorner +with improvements by Pat Terry, Rhodes University + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) any +later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +As an exception, it is allowed to write an extension of Coco/R that is +used as a plugin in non-free software. + +If not otherwise stated, any source code generated by Coco/R (other than +Coco/R itself) does not fall under the GNU General Public License. +------------------------------------------------------------------------*/ +-->begin + +public class Parser { +-->constants + static final boolean _T = true; + static final boolean _x = false; + static final int minErrDist = 2; + + public Token t; // last recognized token + public Token la; // lookahead token + int errDist = minErrDist; + + public Scanner scanner; + public Errors errors; + + public boolean hasErrors() { + return errors.errors.size() > 0; + } + + public String getErrors() { + StringBuilder sb = new StringBuilder(); + for (String s : errors.errors) { + sb.append(s); + sb.append('\n'); + } + return sb.toString(); + } + + + -->declarations + + public Parser(Scanner scanner) { + this.scanner = scanner; + errors = new Errors(); + } + + void SynErr (int n) { + if (errDist >= minErrDist) errors.SynErr(la.line, la.col, n); + errDist = 0; + } + + public void SemErr (String msg) { + if (errDist >= minErrDist) errors.SemErr(t.line, t.col, msg); + errDist = 0; + } + + void Get () { + for (;;) { + t = la; + la = scanner.Scan(); + if (la.kind <= maxT) { ++errDist; break; } +-->pragmas + la = t; + } + } + + void Expect (int n) { + if (la.kind==n) Get(); else { SynErr(n); } + } + + boolean StartOf (int s) { + return set[s][la.kind]; + } + + void ExpectWeak (int n, int follow) { + if (la.kind == n) Get(); + else { + SynErr(n); + while (!StartOf(follow)) Get(); + } + } + + boolean WeakSeparator (int n, int syFol, int repFol) { + int kind = la.kind; + if (kind == n) { Get(); return true; } + else if (StartOf(repFol)) return false; + else { + SynErr(n); + while (!(set[syFol][kind] || set[repFol][kind] || set[0][kind])) { + Get(); + kind = la.kind; + } + return StartOf(syFol); + } + } + +-->productions + + public void Parse() { + la = new Token(); + la.val = ""; + Get(); +-->parseRoot + Expect(0); + } + + private boolean[][] set = { +-->initialization + }; +} // end Parser + + +class Errors { + public String errMsgFormat = "-- line {0} col {1}: {2}"; // 0=line, 1=column, 2=text + public ArrayList errors = new ArrayList(); + + protected void printMsg(int line, int column, String msg) { + StringBuffer b = new StringBuffer(errMsgFormat); + int pos = b.indexOf("{0}"); + if (pos >= 0) { b.delete(pos, pos+3); b.insert(pos, line); } + pos = b.indexOf("{1}"); + if (pos >= 0) { b.delete(pos, pos+3); b.insert(pos, column); } + pos = b.indexOf("{2}"); + if (pos >= 0) b.replace(pos, pos+3, msg); + printMsg(b.toString()); + } + + protected void printMsg(String msg) { + if (errors.size() < 10) { + errors.add(msg); + } + } + + public void SynErr (int line, int col, int n) { + String s; + switch (n) {-->errors + default: s = "error " + n; break; + } + printMsg(line, col, s); + } + + public void SemErr (int line, int col, String s) { + printMsg(line, col, s); + } + + public void SemErr (String s) { + printMsg(s); + } + + public void Warning (int line, int col, String s) { + printMsg(line, col, s); + } + + public void Warning (String s) { + printMsg(s); + } +} // Errors + + +class FatalError extends RuntimeException { + public FatalError(String s) { super(s); } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Parser.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Parser.java new file mode 100644 index 000000000000..fb141032b52f --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Parser.java @@ -0,0 +1,721 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.parser; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Date; +import java.util.List; + +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.modelimpl.CompilationImpl; +import at.ssw.visualizer.modelimpl.CompilationModelImpl; +import at.ssw.visualizer.modelimpl.cfg.ControlFlowGraphImpl; +import at.ssw.visualizer.modelimpl.cfg.IRInstructionImpl; +import at.ssw.visualizer.modelimpl.cfg.StateImpl; +import at.ssw.visualizer.modelimpl.cfg.StateEntryImpl; +import at.ssw.visualizer.modelimpl.interval.IntervalListImpl; +import at.ssw.visualizer.modelimpl.interval.RangeImpl; +import at.ssw.visualizer.modelimpl.interval.UsePositionImpl; +import at.ssw.visualizer.modelimpl.nc.NativeMethodImpl; +import at.ssw.visualizer.modelimpl.bc.BytecodesImpl; + + + +public class Parser { + public static final int _EOF = 0; + public static final int _ident = 1; + public static final int maxT = 54; + + static final boolean _T = true; + static final boolean _x = false; + static final int minErrDist = 2; + + public Token t; // last recognized token + public Token la; // lookahead token + int errDist = minErrDist; + + public Scanner scanner; + public Errors errors; + + public boolean hasErrors() { + return errors.errors.size() > 0; + } + + public String getErrors() { + StringBuilder sb = new StringBuilder(); + for (String s : errors.errors) { + sb.append(s); + sb.append('\n'); + } + return sb.toString(); + } + + + private CompilationModelImpl compilationModel; + +public void setCompilationModel(CompilationModelImpl compilationModel) { + this.compilationModel = compilationModel; +} + +private String simpleName(String className) { + int index = className.lastIndexOf('.'); + if (index < 0) { + return className; + } + return className.substring(index + 1); +} + +private String shortName(String name) { + name = longName(name); + String params = ""; + + int openParam = name.indexOf('('); + if (openParam >= 0) { + int closeParam = name.indexOf(')', openParam); + if (closeParam >= 0) { + String[] parts = name.substring(openParam + 1, closeParam).split(", *"); + for (int i = 0; i < parts.length; i++) { + if (!params.isEmpty()) { + params += ","; + } + params += simpleName(parts[i]); + } + params = "(" + params + ")"; + } + name = name.substring(0, openParam); + } + + int methodPoint = name.lastIndexOf("."); + if (methodPoint < 0) { + return name + params; + } + int classPoint = name.lastIndexOf(".", methodPoint - 1); + if (classPoint < 0) { + return name + params; + } + return name.substring(classPoint + 1) + params; +} + +private String longName(String name) { + return name.replace("::", "."); +} + + + + public Parser(Scanner scanner) { + this.scanner = scanner; + errors = new Errors(); + } + + void SynErr (int n) { + if (errDist >= minErrDist) errors.SynErr(la.line, la.col, n); + errDist = 0; + } + + public void SemErr (String msg) { + if (errDist >= minErrDist) errors.SemErr(t.line, t.col, msg); + errDist = 0; + } + + void Get () { + for (;;) { + t = la; + la = scanner.Scan(); + if (la.kind <= maxT) { ++errDist; break; } + + la = t; + } + } + + void Expect (int n) { + if (la.kind==n) Get(); else { SynErr(n); } + } + + boolean StartOf (int s) { + return set[s][la.kind]; + } + + void ExpectWeak (int n, int follow) { + if (la.kind == n) Get(); + else { + SynErr(n); + while (!StartOf(follow)) Get(); + } + } + + boolean WeakSeparator (int n, int syFol, int repFol) { + int kind = la.kind; + if (kind == n) { Get(); return true; } + else if (StartOf(repFol)) return false; + else { + SynErr(n); + while (!(set[syFol][kind] || set[repFol][kind] || set[0][kind])) { + Get(); + kind = la.kind; + } + return StartOf(syFol); + } + } + + void InputFile() { + CompilationHelper lastComp = null; + ControlFlowGraphImpl lastCFG = null; + IntervalListImpl lastIL = null; + while (StartOf(1)) { + if (la.kind == 2) { + CompilationHelper curComp = Compilation(); + if (lastComp != null) compilationModel.addCompilation(lastComp.resolve()); + lastComp = curComp; + } else if (la.kind == 7) { + lastCFG = CFG(lastComp); + } else if (la.kind == 46) { + lastIL = IntervalList(lastCFG); + lastComp.elements.add(lastIL); + } else if (la.kind == 49) { + NativeMethod(lastCFG); + } else { + Bytecodes(lastCFG); + } + } + if (lastComp != null) compilationModel.addCompilation(lastComp.resolve()); + } + + CompilationHelper Compilation() { + CompilationHelper helper; + helper = new CompilationHelper(); + Expect(2); + Expect(3); + String name = StringValue(); + helper.name = longName(name); helper.shortName = shortName(name); + Expect(4); + String method = StringValue(); + helper.method = longName(method); + Expect(5); + helper.date = DateValue(); + Expect(6); + return helper; + } + + ControlFlowGraphImpl CFG(CompilationHelper lastComp) { + ControlFlowGraphImpl res; + CFGHelper helper = new CFGHelper(); + Expect(7); + Expect(3); + String name = StringValue(); + helper.name = longName(name); helper.shortName = shortName(name); + if (la.kind == 8) { + Get(); + helper.id = IntegerValue(); + Expect(9); + helper.parentId = IntegerValue(); + } + while (la.kind == 11) { + BBHelper basicBlock = BasicBlock(); + helper.add(basicBlock); + } + res = helper.resolve(lastComp, this); + Expect(10); + return res; + } + + IntervalListImpl IntervalList(ControlFlowGraph controlFlowGraph) { + IntervalListImpl res; + IntervalListHelper helper = new IntervalListHelper(); IntervalHelper interval; + if (controlFlowGraph == null) SemErr("must have CFG before intervals"); + Expect(46); + Expect(3); + String name = StringValue(); + helper.name = longName(name); helper.shortName = shortName(name); + while (la.kind == 1) { + interval = Interval(); + helper.add(interval); + } + res = helper.resolve(this, controlFlowGraph); + Expect(47); + return res; + } + + void NativeMethod(ControlFlowGraphImpl cfg) { + Expect(49); + String res = NoTrimFreeValue(); + cfg.setNativeMethod(new NativeMethodImpl(cfg, res)); + Expect(50); + } + + void Bytecodes(ControlFlowGraphImpl cfg) { + Expect(51); + String res = FreeValue(); + cfg.setBytecodes(new BytecodesImpl(cfg, res)); + Expect(52); + } + + String StringValue() { + String res; + Expect(53); + int beg = la.pos; + while (StartOf(2)) { + Get(); + } + res = scanner.buffer.GetString(beg, la.pos).trim().intern(); + Expect(53); + return res; + } + + Date DateValue() { + Date res; + res = null; + Expect(1); + try { res = new Date(Long.parseLong(t.val)); } catch (NumberFormatException ex) { SemErr(t.val); } + return res; + } + + int IntegerValue() { + int res; + res = 0; + Expect(1); + try { res = Integer.parseInt(t.val); } catch (NumberFormatException ex) { SemErr(t.val); } + return res; + } + + BBHelper BasicBlock() { + BBHelper helper; + helper = new BBHelper(); + Expect(11); + Expect(3); + helper.name = StringValue(); + Expect(12); + helper.fromBci = IntegerValue(); + Expect(13); + helper.toBci = IntegerValue(); + Expect(14); + helper.predecessors = StringList(); + Expect(15); + helper.successors = StringList(); + Expect(16); + helper.xhandlers = StringList(); + Expect(17); + helper.flags = StringList(); + if (la.kind == 18) { + Get(); + helper.dominator = StringValue(); + } + if (la.kind == 19) { + Get(); + helper.loopIndex = IntegerValue(); + } + if (la.kind == 20) { + Get(); + helper.loopDepth = IntegerValue(); + } + if (la.kind == 21) { + Get(); + helper.firstLirId = IntegerValue(); + } + if (la.kind == 22) { + Get(); + helper.lastLirId = IntegerValue(); + } + if (la.kind == 23) { + Get(); + helper.probability = DoubleValue(); + } + if (la.kind == 25) { + StateList(helper); + } + if (la.kind == 36) { + HIR(helper); + } + if (la.kind == 39) { + LIR(helper); + } + while (la.kind == 41) { + IR(helper); + } + Expect(24); + return helper; + } + + String[] StringList() { + String[] res; + ArrayList list = new ArrayList(); String item; + while (la.kind == 53) { + item = StringValue(); + list.add(item); + } + res = list.toArray(new String[list.size()]); + return res; + } + + double DoubleValue() { + double res; + res = Double.NaN; + Expect(1); + try { res = Double.longBitsToDouble(Long.parseLong(t.val)); } catch (NumberFormatException ex) { SemErr(t.val); } + return res; + } + + void StateList(BBHelper helper) { + Expect(25); + while (la.kind == 26 || la.kind == 28 || la.kind == 30) { + if (la.kind == 26) { + Get(); + StateImpl state = State("Operands"); + helper.states.add(state); + Expect(27); + } else if (la.kind == 28) { + Get(); + StateImpl state = State("Locks"); + helper.states.add(state); + Expect(29); + } else { + Get(); + StateImpl state = State("Locals"); + helper.states.add(state); + Expect(31); + } + } + Expect(32); + } + + void HIR(BBHelper helper) { + Expect(36); + while (la.kind == 1 || la.kind == 38) { + IRInstructionImpl ins = HIRInstruction(); + helper.hirInstructions.add(ins); + } + Expect(37); + } + + void LIR(BBHelper helper) { + Expect(39); + while (la.kind == 1) { + IRInstructionImpl op = LIROperation(); + helper.lirOperations.add(op); + } + Expect(40); + } + + void IR(BBHelper helper) { + Expect(41); + if (la.kind == 42) { + Get(); + while (la.kind == 1 || la.kind == 45) { + IRInstructionImpl op = IRInstruction(); + helper.hirInstructions.add(op); + } + } else if (la.kind == 43) { + Get(); + while (la.kind == 1 || la.kind == 45) { + IRInstructionImpl op = IRInstruction(); + helper.lirOperations.add(op); + } + } else SynErr(55); + Expect(44); + } + + StateImpl State(String kind) { + StateImpl res; + String method = ""; ArrayList entries = new ArrayList(); + Expect(33); + int size = IntegerValue(); + if (la.kind == 4) { + Get(); + method = StringValue(); + } + while (la.kind == 1) { + StateEntryImpl entry = StateEntry(); + entries.add(entry); + } + res = new StateImpl(kind, size, longName(method), entries.toArray(new StateEntryImpl[entries.size()])); + return res; + } + + StateEntryImpl StateEntry() { + StateEntryImpl res; + String[] operands = null; String operand = null; + int index = IntegerValue(); + String name = HIRName(); + if (la.kind == 34) { + Get(); + ArrayList operandsList = new ArrayList(); + while (la.kind == 1) { + String opd = HIRName(); + operandsList.add(opd); + } + Expect(35); + operands = operandsList.toArray(new String[operandsList.size()]); + } + if (la.kind == 53) { + operand = StringValue(); + } + res = new StateEntryImpl(index, name, operands, operand); + return res; + } + + String HIRName() { + String res; + res = IdentValue(); + if (res.charAt(0) >= '0' && res.charAt(0) <= '9') { res = "v" + res; res = res.intern(); } + return res; + } + + IRInstructionImpl HIRInstruction() { + IRInstructionImpl res; + String pinned = ""; String operand = null; + if (la.kind == 38) { + Get(); + pinned = "."; + } + int bci = IntegerValue(); + int useCount = IntegerValue(); + if (la.kind == 53) { + operand = StringValue(); + } + String name = HIRName(); + String text = FreeValue(); + res = new IRInstructionImpl(pinned, bci, useCount, name, text, operand); + return res; + } + + String FreeValue() { + String res; + int beg = la.pos; + while (StartOf(3)) { + Get(); + } + res = scanner.buffer.GetString(beg, la.pos).trim(); if (res.indexOf('\r') != -1) { res = res.replace("\r\n", "\n"); } res = res.intern(); + Expect(45); + return res; + } + + String IdentValue() { + String res; + Expect(1); + res = t.val.trim().intern(); + return res; + } + + IRInstructionImpl LIROperation() { + IRInstructionImpl res; + int number = IntegerValue(); + String text = FreeValue(); + res = new IRInstructionImpl(number, text); + return res; + } + + IRInstructionImpl IRInstruction() { + IRInstructionImpl res; + LinkedHashMap data = new LinkedHashMap(); + while (la.kind == 1) { + String name = IdentValue(); + String value = FreeValue(); + data.put(name.intern(), value.intern()); + } + Expect(45); + res = new IRInstructionImpl(data); + return res; + } + + IntervalHelper Interval() { + IntervalHelper helper; + helper = new IntervalHelper(); RangeImpl range; UsePositionImpl usePosition; + helper.regNum = IdentValue(); + helper.type = IdentValue(); + if (la.kind == 53) { + helper.operand = StringValue(); + } + helper.splitParent = IdentValue(); + helper.registerHint = IdentValue(); + range = Range(); + helper.ranges.add(range); + while (la.kind == 34) { + range = Range(); + helper.ranges.add(range); + } + while (la.kind == 1) { + usePosition = UsePosition(); + helper.usePositions.add(usePosition); + } + helper.spillState = StringValue(); + return helper; + } + + RangeImpl Range() { + RangeImpl res; + Expect(34); + int from = IntegerValue(); + Expect(48); + int to = IntegerValue(); + Expect(34); + res = new RangeImpl(from, to); + return res; + } + + UsePositionImpl UsePosition() { + UsePositionImpl res; + int position = IntegerValue(); + String kindStr = IdentValue(); + res = new UsePositionImpl(position, kindStr.charAt(0)); + return res; + } + + String NoTrimFreeValue() { + String res; + int beg = la.pos; + while (StartOf(3)) { + Get(); + } + res = scanner.buffer.GetString(beg, la.pos); if (res.indexOf('\r') != -1) { res = res.replace("\r\n", "\n"); } res = res.intern(); + Expect(45); + return res; + } + + + + public void Parse() { + la = new Token(); + la.val = ""; + Get(); + InputFile(); + Expect(0); + + Expect(0); + } + + private boolean[][] set = { + {_T,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x}, + {_x,_x,_T,_x, _x,_x,_x,_T, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_x,_x, _x,_x,_T,_x, _x,_T,_x,_T, _x,_x,_x,_x}, + {_x,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_x,_T,_x}, + {_x,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_T,_T,_T, _T,_x,_T,_T, _T,_T,_T,_T, _T,_T,_T,_x} + + }; +} // end Parser + + +class Errors { + public String errMsgFormat = "-- line {0} col {1}: {2}"; // 0=line, 1=column, 2=text + public ArrayList errors = new ArrayList(); + + protected void printMsg(int line, int column, String msg) { + StringBuffer b = new StringBuffer(errMsgFormat); + int pos = b.indexOf("{0}"); + if (pos >= 0) { b.delete(pos, pos+3); b.insert(pos, line); } + pos = b.indexOf("{1}"); + if (pos >= 0) { b.delete(pos, pos+3); b.insert(pos, column); } + pos = b.indexOf("{2}"); + if (pos >= 0) b.replace(pos, pos+3, msg); + printMsg(b.toString()); + } + + protected void printMsg(String msg) { + if (errors.size() < 10) { + errors.add(msg); + } + } + + public void SynErr (int line, int col, int n) { + String s; + switch (n) { + case 0: s = "EOF expected"; break; + case 1: s = "ident expected"; break; + case 2: s = "\"begin_compilation\" expected"; break; + case 3: s = "\"name\" expected"; break; + case 4: s = "\"method\" expected"; break; + case 5: s = "\"date\" expected"; break; + case 6: s = "\"end_compilation\" expected"; break; + case 7: s = "\"begin_cfg\" expected"; break; + case 8: s = "\"id\" expected"; break; + case 9: s = "\"caller_id\" expected"; break; + case 10: s = "\"end_cfg\" expected"; break; + case 11: s = "\"begin_block\" expected"; break; + case 12: s = "\"from_bci\" expected"; break; + case 13: s = "\"to_bci\" expected"; break; + case 14: s = "\"predecessors\" expected"; break; + case 15: s = "\"successors\" expected"; break; + case 16: s = "\"xhandlers\" expected"; break; + case 17: s = "\"flags\" expected"; break; + case 18: s = "\"dominator\" expected"; break; + case 19: s = "\"loop_index\" expected"; break; + case 20: s = "\"loop_depth\" expected"; break; + case 21: s = "\"first_lir_id\" expected"; break; + case 22: s = "\"last_lir_id\" expected"; break; + case 23: s = "\"probability\" expected"; break; + case 24: s = "\"end_block\" expected"; break; + case 25: s = "\"begin_states\" expected"; break; + case 26: s = "\"begin_stack\" expected"; break; + case 27: s = "\"end_stack\" expected"; break; + case 28: s = "\"begin_locks\" expected"; break; + case 29: s = "\"end_locks\" expected"; break; + case 30: s = "\"begin_locals\" expected"; break; + case 31: s = "\"end_locals\" expected"; break; + case 32: s = "\"end_states\" expected"; break; + case 33: s = "\"size\" expected"; break; + case 34: s = "\"[\" expected"; break; + case 35: s = "\"]\" expected"; break; + case 36: s = "\"begin_HIR\" expected"; break; + case 37: s = "\"end_HIR\" expected"; break; + case 38: s = "\".\" expected"; break; + case 39: s = "\"begin_LIR\" expected"; break; + case 40: s = "\"end_LIR\" expected"; break; + case 41: s = "\"begin_IR\" expected"; break; + case 42: s = "\"HIR\" expected"; break; + case 43: s = "\"LIR\" expected"; break; + case 44: s = "\"end_IR\" expected"; break; + case 45: s = "\"<|@\" expected"; break; + case 46: s = "\"begin_intervals\" expected"; break; + case 47: s = "\"end_intervals\" expected"; break; + case 48: s = "\",\" expected"; break; + case 49: s = "\"begin_nmethod\" expected"; break; + case 50: s = "\"end_nmethod\" expected"; break; + case 51: s = "\"begin_bytecodes\" expected"; break; + case 52: s = "\"end_bytecodes\" expected"; break; + case 53: s = "\"\\\"\" expected"; break; + case 54: s = "??? expected"; break; + case 55: s = "invalid IR"; break; + default: s = "error " + n; break; + } + printMsg(line, col, s); + } + + public void SemErr (int line, int col, String s) { + printMsg(line, col, s); + } + + public void SemErr (String s) { + printMsg(s); + } + + public void Warning (int line, int col, String s) { + printMsg(line, col, s); + } + + public void Warning (String s) { + printMsg(s); + } +} // Errors + + +class FatalError extends RuntimeException { + public FatalError(String s) { super(s); } +} diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Scanner.frame b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Scanner.frame new file mode 100644 index 000000000000..fd8793afda2c --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Scanner.frame @@ -0,0 +1,463 @@ +/*------------------------------------------------------------------------- +Compiler Generator Coco/R, +Copyright (c) 1990, 2004 Hanspeter Moessenboeck, University of Linz +extended by M. Loeberbauer & A. Woess, Univ. of Linz +ported from C# to Java by Wolfgang Ahorner +with improvements by Pat Terry, Rhodes University + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) any +later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +As an exception, it is allowed to write an extension of Coco/R that is +used as a plugin in non-free software. + +If not otherwise stated, any source code generated by Coco/R (other than +Coco/R itself) does not fall under the GNU General Public License. +------------------------------------------------------------------------*/ +-->begin +import java.io.InputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Arrays; +import java.util.Map; +import java.util.HashMap; +import org.netbeans.api.progress.ProgressHandle; + +class Token { + public int kind; // token kind + public int pos; // token position in bytes in the source text (starting at 0) + public int charPos; // token position in characters in the source text (starting at 0) + public int col; // token column (starting at 1) + public int line; // token line (starting at 1) + public String val; // token value + public Token next; // ML 2005-03-11 Peek tokens are kept in linked list +} + +//----------------------------------------------------------------------------------- +// Buffer +//----------------------------------------------------------------------------------- +class Buffer { + // This Buffer supports the following cases: + // 1) seekable stream (file) + // a) whole stream in buffer + // b) part of stream in buffer + // 2) non seekable stream (network, console) + + public static final int EOF = Character.MAX_VALUE + 1; + private static final int MIN_BUFFER_LENGTH = 1024; // 1KB + private static final int MAX_BUFFER_LENGTH = MIN_BUFFER_LENGTH * 64; // 64KB + private byte[] buf; // input buffer + private int bufStart; // position of first byte in buffer relative to input stream + private int bufLen; // length of buffer + private int fileLen; // length of input stream (may change if stream is no file) + private int bufPos; // current position in buffer + private RandomAccessFile file; // input stream (seekable) + private InputStream stream; // growing input stream (e.g.: console, network) + ProgressHandle progressHandle; + int maxSetPosValue; + + public Buffer(InputStream s) { + stream = s; + fileLen = bufLen = bufStart = bufPos = 0; + buf = new byte[MIN_BUFFER_LENGTH]; + } + + public Buffer(String fileName, ProgressHandle progressHandle) { + this.progressHandle = progressHandle; + try { + file = new RandomAccessFile(fileName, "r"); + fileLen = (int) file.length(); + if (progressHandle != null) { + progressHandle.start((int)(fileLen / MAX_BUFFER_LENGTH)); + } + bufLen = Math.min(fileLen, MAX_BUFFER_LENGTH); + buf = new byte[bufLen]; + bufStart = Integer.MAX_VALUE; // nothing in buffer so far + if (fileLen > 0) setPos(0); // setup buffer to position 0 (start) + else bufPos = 0; // index 0 is already after the file, thus setPos(0) is invalid + if (bufLen == fileLen) Close(); + } catch (IOException e) { + throw new FatalError("Could not open file " + fileName); + } + } + + // don't use b after this call anymore + // called in UTF8Buffer constructor + protected Buffer(Buffer b) { + buf = b.buf; + bufStart = b.bufStart; + bufLen = b.bufLen; + fileLen = b.fileLen; + bufPos = b.bufPos; + file = b.file; + stream = b.stream; + // keep finalize from closing the file + b.file = null; + } + + protected void finalize() throws Throwable { + super.finalize(); + Close(); + } + + protected void Close() { + if (file != null) { + try { + file.close(); + file = null; + } catch (IOException e) { + throw new FatalError(e.getMessage()); + } + } + } + + public int Read() { + if (bufPos < bufLen) { + return buf[bufPos++] & 0xff; // mask out sign bits + } else if (getPos() < fileLen) { + setPos(getPos()); // shift buffer start to pos + return buf[bufPos++] & 0xff; // mask out sign bits + } else if (stream != null && ReadNextStreamChunk() > 0) { + return buf[bufPos++] & 0xff; // mask out sign bits + } else { + return EOF; + } + } + + public int Peek() { + int curPos = getPos(); + int ch = Read(); + setPos(curPos); + return ch; + } + + // beg .. begin, zero-based, inclusive, in byte + // end .. end, zero-based, exclusive, in byte + public String GetString(int beg, int end) { + int len = 0; + char[] buf = new char[end - beg]; + int oldPos = getPos(); + setPos(beg); + while (getPos() < end) buf[len++] = (char) Read(); + setPos(oldPos); + return new String(buf, 0, len); + } + + public int getPos() { + return bufPos + bufStart; + } + + public void setPos(int value) { + if (value >= fileLen && stream != null) { + // Wanted position is after buffer and the stream + // is not seek-able e.g. network or console, + // thus we have to read the stream manually till + // the wanted position is in sight. + while (value >= fileLen && ReadNextStreamChunk() > 0); + } + + if (value < 0 || value > fileLen) { + throw new FatalError("buffer out of bounds access, position: " + value); + } + + if (value >= bufStart && value < bufStart + bufLen) { // already in buffer + bufPos = value - bufStart; + } else if (file != null) { // must be swapped in + try { + file.seek(value); + bufLen = file.read(buf); + bufStart = value; bufPos = 0; + } catch(IOException e) { + throw new FatalError(e.getMessage()); + } + int newPosValue = (int)(value / MAX_BUFFER_LENGTH); + if (progressHandle != null && newPosValue > maxSetPosValue) { + progressHandle.progress(newPosValue); + maxSetPosValue = newPosValue; + } + } else { + // set the position to the end of the file, Pos will return fileLen. + bufPos = fileLen - bufStart; + } + } + + // Read the next chunk of bytes from the stream, increases the buffer + // if needed and updates the fields fileLen and bufLen. + // Returns the number of bytes read. + private int ReadNextStreamChunk() { + int free = buf.length - bufLen; + if (free == 0) { + // in the case of a growing input stream + // we can neither seek in the stream, nor can we + // foresee the maximum length, thus we must adapt + // the buffer size on demand. + byte[] newBuf = new byte[bufLen * 2]; + System.arraycopy(buf, 0, newBuf, 0, bufLen); + buf = newBuf; + free = bufLen; + } + + int read; + try { read = stream.read(buf, bufLen, free); } + catch (IOException ioex) { throw new FatalError(ioex.getMessage()); } + + if (read > 0) { + fileLen = bufLen = (bufLen + read); + return read; + } + // end of stream reached + return 0; + } +} + +//----------------------------------------------------------------------------------- +// UTF8Buffer +//----------------------------------------------------------------------------------- +class UTF8Buffer extends Buffer { + UTF8Buffer(Buffer b) { super(b); } + + public int Read() { + int ch; + do { + ch = super.Read(); + // until we find a utf8 start (0xxxxxxx or 11xxxxxx) + } while ((ch >= 128) && ((ch & 0xC0) != 0xC0) && (ch != EOF)); + if (ch < 128 || ch == EOF) { + // nothing to do, first 127 chars are the same in ascii and utf8 + // 0xxxxxxx or end of file character + } else if ((ch & 0xF0) == 0xF0) { + // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + int c1 = ch & 0x07; ch = super.Read(); + int c2 = ch & 0x3F; ch = super.Read(); + int c3 = ch & 0x3F; ch = super.Read(); + int c4 = ch & 0x3F; + ch = (((((c1 << 6) | c2) << 6) | c3) << 6) | c4; + } else if ((ch & 0xE0) == 0xE0) { + // 1110xxxx 10xxxxxx 10xxxxxx + int c1 = ch & 0x0F; ch = super.Read(); + int c2 = ch & 0x3F; ch = super.Read(); + int c3 = ch & 0x3F; + ch = (((c1 << 6) | c2) << 6) | c3; + } else if ((ch & 0xC0) == 0xC0) { + // 110xxxxx 10xxxxxx + int c1 = ch & 0x1F; ch = super.Read(); + int c2 = ch & 0x3F; + ch = (c1 << 6) | c2; + } + return ch; + } +} + +//----------------------------------------------------------------------------------- +// StartStates -- maps characters to start states of tokens +//----------------------------------------------------------------------------------- +class StartStates { + private static class Elem { + public int key, val; + public Elem next; + public Elem(int key, int val) { this.key = key; this.val = val; } + } + + private Elem[] tab = new Elem[128]; + + public void set(int key, int val) { + Elem e = new Elem(key, val); + int k = key % 128; + e.next = tab[k]; tab[k] = e; + } + + public int state(int key) { + Elem e = tab[key % 128]; + while (e != null && e.key != key) e = e.next; + return e == null ? 0: e.val; + } +} + +//----------------------------------------------------------------------------------- +// Scanner +//----------------------------------------------------------------------------------- +public class Scanner { + static final char EOL = '\n'; + static final int eofSym = 0; +-->declarations + + public Buffer buffer; // scanner buffer + + Token t; // current token + int ch; // current input character + int pos; // byte position of current character + int charPos; // position by unicode characters starting with 0 + int col; // column number of current character + int line; // line number of current character + int oldEols; // EOLs that appeared in a comment; + static final StartStates start; // maps initial token character to start state + static final Map literals; // maps literal strings to literal kinds + static int maxLiteral; + static boolean[] literalFirstChar; + + Token tokens; // list of tokens already peeked (first token is a dummy) + Token pt; // current peek token + + char[] tval = new char[16]; // token text used in NextToken(), dynamically enlarged + int tlen; // length of current token + + + static { + start = new StartStates(); + literals = new HashMap(); +-->initialization + literalFirstChar = new boolean[0]; + for (String literal : literals.keySet()) { + maxLiteral = Math.max(literal.length(), maxLiteral); + char c = literal.charAt(0); + if (c >= literalFirstChar.length) { + literalFirstChar = Arrays.copyOf(literalFirstChar, c + 1); + } + literalFirstChar[c] = true; + } + maxLiteral = 0; + literalFirstChar = null; + } + + public Scanner (String fileName, ProgressHandle progressHandle) { + buffer = new Buffer(fileName, progressHandle); + Init(); + } + + public Scanner(InputStream s) { + buffer = new Buffer(s); + Init(); + } + + void Init () { + pos = -1; line = 1; col = 0; charPos = -1; + oldEols = 0; + NextCh(); + if (ch == 0xEF) { // check optional byte order mark for UTF-8 + NextCh(); int ch1 = ch; + NextCh(); int ch2 = ch; + if (ch1 != 0xBB || ch2 != 0xBF) { + throw new FatalError("Illegal byte order mark at start of file"); + } + buffer = new UTF8Buffer(buffer); col = 0; charPos = -1; + NextCh(); + } + pt = tokens = new Token(); // first token is a dummy + } + + void NextCh() { + if (oldEols > 0) { ch = EOL; oldEols--; } + else { + pos = buffer.getPos(); + // buffer reads unicode chars, if UTF8 has been detected + ch = buffer.Read(); col++; charPos++; + // replace isolated '\r' by '\n' in order to make + // eol handling uniform across Windows, Unix and Mac + if (ch == '\r' && buffer.Peek() != '\n') ch = EOL; + if (ch == EOL) { line++; col = 0; } + } +-->casing + } + + void AddCh() { + if (tlen >= tval.length) { + char[] newBuf = new char[2 * tval.length]; + System.arraycopy(tval, 0, newBuf, 0, tval.length); + tval = newBuf; + } + if (ch != Buffer.EOF) { +-->casing2 + NextCh(); + } + + } + +-->comments + + void CheckLiteral() { + String val = t.val; +-->casing3 +// if (maxLiteral == 0 || val.length() <= maxLiteral && val.length() > 0) { +// char c = val.charAt(0); +// if (literalFirstChar == null || c < literalFirstChar.length && literalFirstChar[c]) { + Object kind = literals.get(val); + if (kind != null) { + t.kind = ((Integer) kind).intValue(); + } +// } +// } + } + + Token NextToken() { + while (ch == ' ' || +-->scan1 + ) NextCh(); +-->scan2 + int recKind = noSym; + int recEnd = pos; + t = new Token(); + t.pos = pos; t.col = col; t.line = line; t.charPos = charPos; + int state = start.state(ch); + tlen = 0; AddCh(); + + loop: for (;;) { + switch (state) { + case -1: { t.kind = eofSym; break loop; } // NextCh already done + case 0: { + if (recKind != noSym) { + tlen = recEnd - t.pos; + SetScannerBehindT(); + } + t.kind = recKind; break loop; + } // NextCh already done +-->scan3 + } + } + t.val = new String(tval, 0, tlen); + return t; + } + + private void SetScannerBehindT() { + buffer.setPos(t.pos); + NextCh(); + line = t.line; col = t.col; charPos = t.charPos; + for (int i = 0; i < tlen; i++) NextCh(); + } + + // get the next token (possibly a token already seen during peeking) + public Token Scan () { + if (tokens.next == null) { + return NextToken(); + } else { + pt = tokens = tokens.next; + return tokens; + } + } + + // get the next token, ignore pragmas + public Token Peek () { + do { + if (pt.next == null) { + pt.next = NextToken(); + } + pt = pt.next; + } while (pt.kind > maxT); // skip pragmas + + return pt; + } + + // make sure that peeking starts at current scan position + public void ResetPeek () { pt = tokens; } + +} // end Scanner diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Scanner.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Scanner.java new file mode 100644 index 000000000000..cb979bb9bb5d --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Scanner.java @@ -0,0 +1,544 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.parser; + +import java.io.InputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Arrays; +import java.util.Map; +import java.util.HashMap; +import org.netbeans.api.progress.ProgressHandle; + +class Token { + public int kind; // token kind + public int pos; // token position in bytes in the source text (starting at 0) + public int charPos; // token position in characters in the source text (starting at 0) + public int col; // token column (starting at 1) + public int line; // token line (starting at 1) + public String val; // token value + public Token next; // ML 2005-03-11 Peek tokens are kept in linked list +} + +//----------------------------------------------------------------------------------- +// Buffer +//----------------------------------------------------------------------------------- +class Buffer { + // This Buffer supports the following cases: + // 1) seekable stream (file) + // a) whole stream in buffer + // b) part of stream in buffer + // 2) non seekable stream (network, console) + + public static final int EOF = Character.MAX_VALUE + 1; + private static final int MIN_BUFFER_LENGTH = 1024; // 1KB + private static final int MAX_BUFFER_LENGTH = MIN_BUFFER_LENGTH * 64; // 64KB + private byte[] buf; // input buffer + private int bufStart; // position of first byte in buffer relative to input stream + private int bufLen; // length of buffer + private int fileLen; // length of input stream (may change if stream is no file) + private int bufPos; // current position in buffer + private RandomAccessFile file; // input stream (seekable) + private InputStream stream; // growing input stream (e.g.: console, network) + ProgressHandle progressHandle; + int maxSetPosValue; + + public Buffer(InputStream s) { + stream = s; + fileLen = bufLen = bufStart = bufPos = 0; + buf = new byte[MIN_BUFFER_LENGTH]; + } + + public Buffer(String fileName, ProgressHandle progressHandle) { + this.progressHandle = progressHandle; + try { + file = new RandomAccessFile(fileName, "r"); + fileLen = (int) file.length(); + if (progressHandle != null) { + progressHandle.start((int)(fileLen / MAX_BUFFER_LENGTH)); + } + bufLen = Math.min(fileLen, MAX_BUFFER_LENGTH); + buf = new byte[bufLen]; + bufStart = Integer.MAX_VALUE; // nothing in buffer so far + if (fileLen > 0) setPos(0); // setup buffer to position 0 (start) + else bufPos = 0; // index 0 is already after the file, thus setPos(0) is invalid + if (bufLen == fileLen) Close(); + } catch (IOException e) { + throw new FatalError("Could not open file " + fileName); + } + } + + // don't use b after this call anymore + // called in UTF8Buffer constructor + protected Buffer(Buffer b) { + buf = b.buf; + bufStart = b.bufStart; + bufLen = b.bufLen; + fileLen = b.fileLen; + bufPos = b.bufPos; + file = b.file; + stream = b.stream; + // keep finalize from closing the file + b.file = null; + } + + protected void finalize() throws Throwable { + super.finalize(); + Close(); + } + + protected void Close() { + if (file != null) { + try { + file.close(); + file = null; + } catch (IOException e) { + throw new FatalError(e.getMessage()); + } + } + } + + public int Read() { + if (bufPos < bufLen) { + return buf[bufPos++] & 0xff; // mask out sign bits + } else if (getPos() < fileLen) { + setPos(getPos()); // shift buffer start to pos + return buf[bufPos++] & 0xff; // mask out sign bits + } else if (stream != null && ReadNextStreamChunk() > 0) { + return buf[bufPos++] & 0xff; // mask out sign bits + } else { + return EOF; + } + } + + public int Peek() { + int curPos = getPos(); + int ch = Read(); + setPos(curPos); + return ch; + } + + // beg .. begin, zero-based, inclusive, in byte + // end .. end, zero-based, exclusive, in byte + public String GetString(int beg, int end) { + int len = 0; + char[] buf = new char[end - beg]; + int oldPos = getPos(); + setPos(beg); + while (getPos() < end) buf[len++] = (char) Read(); + setPos(oldPos); + return new String(buf, 0, len); + } + + public int getPos() { + return bufPos + bufStart; + } + + public void setPos(int value) { + if (value >= fileLen && stream != null) { + // Wanted position is after buffer and the stream + // is not seek-able e.g. network or console, + // thus we have to read the stream manually till + // the wanted position is in sight. + while (value >= fileLen && ReadNextStreamChunk() > 0); + } + + if (value < 0 || value > fileLen) { + throw new FatalError("buffer out of bounds access, position: " + value); + } + + if (value >= bufStart && value < bufStart + bufLen) { // already in buffer + bufPos = value - bufStart; + } else if (file != null) { // must be swapped in + try { + file.seek(value); + bufLen = file.read(buf); + bufStart = value; bufPos = 0; + } catch(IOException e) { + throw new FatalError(e.getMessage()); + } + int newPosValue = (int)(value / MAX_BUFFER_LENGTH); + if (progressHandle != null && newPosValue > maxSetPosValue) { + progressHandle.progress(newPosValue); + maxSetPosValue = newPosValue; + } + } else { + // set the position to the end of the file, Pos will return fileLen. + bufPos = fileLen - bufStart; + } + } + + // Read the next chunk of bytes from the stream, increases the buffer + // if needed and updates the fields fileLen and bufLen. + // Returns the number of bytes read. + private int ReadNextStreamChunk() { + int free = buf.length - bufLen; + if (free == 0) { + // in the case of a growing input stream + // we can neither seek in the stream, nor can we + // foresee the maximum length, thus we must adapt + // the buffer size on demand. + byte[] newBuf = new byte[bufLen * 2]; + System.arraycopy(buf, 0, newBuf, 0, bufLen); + buf = newBuf; + free = bufLen; + } + + int read; + try { read = stream.read(buf, bufLen, free); } + catch (IOException ioex) { throw new FatalError(ioex.getMessage()); } + + if (read > 0) { + fileLen = bufLen = (bufLen + read); + return read; + } + // end of stream reached + return 0; + } +} + +//----------------------------------------------------------------------------------- +// UTF8Buffer +//----------------------------------------------------------------------------------- +class UTF8Buffer extends Buffer { + UTF8Buffer(Buffer b) { super(b); } + + public int Read() { + int ch; + do { + ch = super.Read(); + // until we find a utf8 start (0xxxxxxx or 11xxxxxx) + } while ((ch >= 128) && ((ch & 0xC0) != 0xC0) && (ch != EOF)); + if (ch < 128 || ch == EOF) { + // nothing to do, first 127 chars are the same in ascii and utf8 + // 0xxxxxxx or end of file character + } else if ((ch & 0xF0) == 0xF0) { + // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + int c1 = ch & 0x07; ch = super.Read(); + int c2 = ch & 0x3F; ch = super.Read(); + int c3 = ch & 0x3F; ch = super.Read(); + int c4 = ch & 0x3F; + ch = (((((c1 << 6) | c2) << 6) | c3) << 6) | c4; + } else if ((ch & 0xE0) == 0xE0) { + // 1110xxxx 10xxxxxx 10xxxxxx + int c1 = ch & 0x0F; ch = super.Read(); + int c2 = ch & 0x3F; ch = super.Read(); + int c3 = ch & 0x3F; + ch = (((c1 << 6) | c2) << 6) | c3; + } else if ((ch & 0xC0) == 0xC0) { + // 110xxxxx 10xxxxxx + int c1 = ch & 0x1F; ch = super.Read(); + int c2 = ch & 0x3F; + ch = (c1 << 6) | c2; + } + return ch; + } +} + +//----------------------------------------------------------------------------------- +// StartStates -- maps characters to start states of tokens +//----------------------------------------------------------------------------------- +class StartStates { + private static class Elem { + public int key, val; + public Elem next; + public Elem(int key, int val) { this.key = key; this.val = val; } + } + + private Elem[] tab = new Elem[128]; + + public void set(int key, int val) { + Elem e = new Elem(key, val); + int k = key % 128; + e.next = tab[k]; tab[k] = e; + } + + public int state(int key) { + Elem e = tab[key % 128]; + while (e != null && e.key != key) e = e.next; + return e == null ? 0: e.val; + } +} + +//----------------------------------------------------------------------------------- +// Scanner +//----------------------------------------------------------------------------------- +public class Scanner { + static final char EOL = '\n'; + static final int eofSym = 0; + static final int maxT = 54; + static final int noSym = 54; + + + public Buffer buffer; // scanner buffer + + Token t; // current token + int ch; // current input character + int pos; // byte position of current character + int charPos; // position by unicode characters starting with 0 + int col; // column number of current character + int line; // line number of current character + int oldEols; // EOLs that appeared in a comment; + static final StartStates start; // maps initial token character to start state + static final Map literals; // maps literal strings to literal kinds + static int maxLiteral; + static boolean[] literalFirstChar; + + Token tokens; // list of tokens already peeked (first token is a dummy) + Token pt; // current peek token + + char[] tval = new char[16]; // token text used in NextToken(), dynamically enlarged + int tlen; // length of current token + + + static { + start = new StartStates(); + literals = new HashMap(); + for (int i = 42; i <= 42; ++i) start.set(i, 1); + for (int i = 45; i <= 45; ++i) start.set(i, 1); + for (int i = 48; i <= 58; ++i) start.set(i, 1); + for (int i = 65; i <= 90; ++i) start.set(i, 1); + for (int i = 95; i <= 95; ++i) start.set(i, 1); + for (int i = 97; i <= 122; ++i) start.set(i, 1); + for (int i = 124; i <= 124; ++i) start.set(i, 1); + start.set(91, 2); + start.set(93, 3); + start.set(46, 4); + start.set(60, 5); + start.set(44, 8); + start.set(34, 9); + start.set(Buffer.EOF, -1); + literals.put("begin_compilation", new Integer(2)); + literals.put("name", new Integer(3)); + literals.put("method", new Integer(4)); + literals.put("date", new Integer(5)); + literals.put("end_compilation", new Integer(6)); + literals.put("begin_cfg", new Integer(7)); + literals.put("id", new Integer(8)); + literals.put("caller_id", new Integer(9)); + literals.put("end_cfg", new Integer(10)); + literals.put("begin_block", new Integer(11)); + literals.put("from_bci", new Integer(12)); + literals.put("to_bci", new Integer(13)); + literals.put("predecessors", new Integer(14)); + literals.put("successors", new Integer(15)); + literals.put("xhandlers", new Integer(16)); + literals.put("flags", new Integer(17)); + literals.put("dominator", new Integer(18)); + literals.put("loop_index", new Integer(19)); + literals.put("loop_depth", new Integer(20)); + literals.put("first_lir_id", new Integer(21)); + literals.put("last_lir_id", new Integer(22)); + literals.put("probability", new Integer(23)); + literals.put("end_block", new Integer(24)); + literals.put("begin_states", new Integer(25)); + literals.put("begin_stack", new Integer(26)); + literals.put("end_stack", new Integer(27)); + literals.put("begin_locks", new Integer(28)); + literals.put("end_locks", new Integer(29)); + literals.put("begin_locals", new Integer(30)); + literals.put("end_locals", new Integer(31)); + literals.put("end_states", new Integer(32)); + literals.put("size", new Integer(33)); + literals.put("begin_HIR", new Integer(36)); + literals.put("end_HIR", new Integer(37)); + literals.put("begin_LIR", new Integer(39)); + literals.put("end_LIR", new Integer(40)); + literals.put("begin_IR", new Integer(41)); + literals.put("HIR", new Integer(42)); + literals.put("LIR", new Integer(43)); + literals.put("end_IR", new Integer(44)); + literals.put("begin_intervals", new Integer(46)); + literals.put("end_intervals", new Integer(47)); + literals.put("begin_nmethod", new Integer(49)); + literals.put("end_nmethod", new Integer(50)); + literals.put("begin_bytecodes", new Integer(51)); + literals.put("end_bytecodes", new Integer(52)); + + literalFirstChar = new boolean[0]; + for (String literal : literals.keySet()) { + maxLiteral = Math.max(literal.length(), maxLiteral); + char c = literal.charAt(0); + if (c >= literalFirstChar.length) { + literalFirstChar = Arrays.copyOf(literalFirstChar, c + 1); + } + literalFirstChar[c] = true; + } + maxLiteral = 0; + literalFirstChar = null; + } + + public Scanner (String fileName, ProgressHandle progressHandle) { + buffer = new Buffer(fileName, progressHandle); + Init(); + } + + public Scanner(InputStream s) { + buffer = new Buffer(s); + Init(); + } + + void Init () { + pos = -1; line = 1; col = 0; charPos = -1; + oldEols = 0; + NextCh(); + if (ch == 0xEF) { // check optional byte order mark for UTF-8 + NextCh(); int ch1 = ch; + NextCh(); int ch2 = ch; + if (ch1 != 0xBB || ch2 != 0xBF) { + throw new FatalError("Illegal byte order mark at start of file"); + } + buffer = new UTF8Buffer(buffer); col = 0; charPos = -1; + NextCh(); + } + pt = tokens = new Token(); // first token is a dummy + } + + void NextCh() { + if (oldEols > 0) { ch = EOL; oldEols--; } + else { + pos = buffer.getPos(); + // buffer reads unicode chars, if UTF8 has been detected + ch = buffer.Read(); col++; charPos++; + // replace isolated '\r' by '\n' in order to make + // eol handling uniform across Windows, Unix and Mac + if (ch == '\r' && buffer.Peek() != '\n') ch = EOL; + if (ch == EOL) { line++; col = 0; } + } + + } + + void AddCh() { + if (tlen >= tval.length) { + char[] newBuf = new char[2 * tval.length]; + System.arraycopy(tval, 0, newBuf, 0, tval.length); + tval = newBuf; + } + if (ch != Buffer.EOF) { + tval[tlen++] = (char)ch; + + NextCh(); + } + + } + + + + void CheckLiteral() { + String val = t.val; + +// if (maxLiteral == 0 || val.length() <= maxLiteral && val.length() > 0) { +// char c = val.charAt(0); +// if (literalFirstChar == null || c < literalFirstChar.length && literalFirstChar[c]) { + Object kind = literals.get(val); + if (kind != null) { + t.kind = ((Integer) kind).intValue(); + } +// } +// } + } + + Token NextToken() { + while (ch == ' ' || + ch == 10 || ch == 13 + ) NextCh(); + + int recKind = noSym; + int recEnd = pos; + t = new Token(); + t.pos = pos; t.col = col; t.line = line; t.charPos = charPos; + int state = start.state(ch); + tlen = 0; AddCh(); + + loop: for (;;) { + switch (state) { + case -1: { t.kind = eofSym; break loop; } // NextCh already done + case 0: { + if (recKind != noSym) { + tlen = recEnd - t.pos; + SetScannerBehindT(); + } + t.kind = recKind; break loop; + } // NextCh already done + case 1: + recEnd = pos; recKind = 1; + if (ch == '*' || ch == '-' || ch >= '0' && ch <= ':' || ch >= 'A' && ch <= 'Z' || ch == '_' || ch >= 'a' && ch <= 'z' || ch == '|') {AddCh(); state = 1; break;} + else {t.kind = 1; t.val = new String(tval, 0, tlen); CheckLiteral(); return t;} + case 2: + {t.kind = 34; break loop;} + case 3: + {t.kind = 35; break loop;} + case 4: + {t.kind = 38; break loop;} + case 5: + if (ch == '|') {AddCh(); state = 6; break;} + else {state = 0; break;} + case 6: + if (ch == '@') {AddCh(); state = 7; break;} + else {state = 0; break;} + case 7: + {t.kind = 45; break loop;} + case 8: + {t.kind = 48; break loop;} + case 9: + {t.kind = 53; break loop;} + + } + } + t.val = new String(tval, 0, tlen); + return t; + } + + private void SetScannerBehindT() { + buffer.setPos(t.pos); + NextCh(); + line = t.line; col = t.col; charPos = t.charPos; + for (int i = 0; i < tlen; i++) NextCh(); + } + + // get the next token (possibly a token already seen during peeking) + public Token Scan () { + if (tokens.next == null) { + return NextToken(); + } else { + pt = tokens = tokens.next; + return tokens; + } + } + + // get the next token, ignore pragmas + public Token Peek () { + do { + if (pt.next == null) { + pt.next = NextToken(); + } + pt = pt.next; + } while (pt.kind > maxT); // skip pragmas + + return pt; + } + + // make sure that peeking starts at current scan position + public void ResetPeek () { pt = tokens; } + +} // end Scanner diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/CompilationModel/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..9a47908b4c88 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/nbm/manifest.mf @@ -0,0 +1,4 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.model +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/model/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/resources/META-INF/services/at.ssw.visualizer.model.CompilationModel b/visualizer/C1Visualizer/CompilationModel/src/main/resources/META-INF/services/at.ssw.visualizer.model.CompilationModel new file mode 100644 index 000000000000..5c61a429118c --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/resources/META-INF/services/at.ssw.visualizer.model.CompilationModel @@ -0,0 +1 @@ +at.ssw.visualizer.modelimpl.CompilationModelImpl \ No newline at end of file diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/resources/at/ssw/visualizer/model/Bundle.properties b/visualizer/C1Visualizer/CompilationModel/src/main/resources/at/ssw/visualizer/model/Bundle.properties new file mode 100644 index 000000000000..876abe05faf5 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationModel/src/main/resources/at/ssw/visualizer/model/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Compilation Model diff --git a/visualizer/C1Visualizer/CompilationView/pom.xml b/visualizer/C1Visualizer/CompilationView/pom.xml new file mode 100644 index 000000000000..42f2b6658f47 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationView/pom.xml @@ -0,0 +1,132 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + CompilationView + 1.13-SNAPSHOT + nbm + CompilationView + + UTF-8 + + + + at.ssw.visualizer + VisualizerUI + ${project.version} + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + org.netbeans.api + org-netbeans-modules-editor-mimelookup + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-sendopts + ${netbeans.version} + + + org.netbeans.api + org-openide-actions + ${netbeans.version} + + + org.netbeans.api + org-openide-awt + ${netbeans.version} + + + org.netbeans.api + org-openide-dialogs + ${netbeans.version} + + + org.netbeans.api + org-openide-explorer + ${netbeans.version} + + + org.netbeans.api + org-openide-filesystems + ${netbeans.version} + + + org.netbeans.api + org-openide-loaders + ${netbeans.version} + + + org.netbeans.api + org-openide-nodes + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-util-ui + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.CompilationView + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/CompilationModelNode.java b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/CompilationModelNode.java new file mode 100644 index 000000000000..5acb276c769b --- /dev/null +++ b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/CompilationModelNode.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.compilation.view; + +import at.ssw.visualizer.compilation.view.icons.Icons; +import at.ssw.visualizer.model.Compilation; +import at.ssw.visualizer.model.CompilationElement; +import at.ssw.visualizer.model.CompilationModel; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import java.awt.Image; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import javax.swing.Action; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.util.ImageUtilities; +import org.openide.util.lookup.Lookups; + +/** + * + * @author Christian Wimmer + */ +public class CompilationModelNode extends AbstractNode { + protected Image ImageFolder = ImageUtilities.loadImage(Icons.FOLDER); + protected Image ImageCfg = ImageUtilities.loadImage(Icons.CFG); + protected Image ImageIntervals = ImageUtilities.loadImage(Icons.INTERVALS); + protected boolean sortCompilations; + protected boolean filterCfg; + protected boolean shortNames; + + public CompilationModelNode(CompilationModel model) { + super(Children.LEAF); + setChildren(new CompilationModelChildren(model)); + } + + public void doRefresh(boolean sortCompilations, boolean filterCfg, boolean shortNames) { + this.sortCompilations = sortCompilations; + this.filterCfg = filterCfg; + this.shortNames = shortNames; + + ((CompilationModelChildren) getChildren()).doRefresh(); + } + + @Override + public Action[] getActions(boolean context) { + return new Action[0]; + } + + class CompilationModelChildren extends Children.Keys { + private CompilationModel model; + private ChangeListener changeListener = new ChangeListener() { + public void stateChanged(ChangeEvent event) { + addNotify(); + } + + }; + + public CompilationModelChildren(CompilationModel model) { + this.model = model; + model.addChangedListener(changeListener); + } + + public void doRefresh() { + addNotify(); + for (Node n : getNodes()) { + ((CompilationNode) n).doRefresh(); + } + } + + @Override + protected void addNotify() { + Compilation[] compilations = model.getCompilations().toArray(new Compilation[0]); + if (sortCompilations) { + Arrays.sort(compilations, new Comparator() { + public int compare(Compilation o1, Compilation o2) { + if (shortNames) { + return o1.getShortName().compareToIgnoreCase(o2.getShortName()); + } + return o1.getName().compareToIgnoreCase(o2.getName()); + } + + }); + } + setKeys(compilations); + } + + protected Node[] createNodes(Compilation key) { + return new Node[]{new CompilationNode(key)}; + } + + } + + class CompilationElementNode extends AbstractNode { + private CompilationElement element; + + public CompilationElementNode(CompilationElement element) { + super(element.getElements().size() > 0 ? new CompilationElementChildren(element) : Children.LEAF, Lookups.singleton(element)); + this.element = element; + } + + public void doRefresh() { + fireNameChange(null, null); + if (getChildren() instanceof CompilationElementChildren) { + ((CompilationElementChildren) getChildren()).doRefresh(); + } + } + + @Override + public String getName() { + if (shortNames) { + return element.getShortName(); + } + return element.getName(); + } + + @Override + public String getShortDescription() { + return element.getName(); + } + + @Override + public Image getIcon(int type) { + return element instanceof ControlFlowGraph ? ImageCfg : ImageIntervals; + } + + @Override + public Image getOpenedIcon(int type) { + return getIcon(type); + } + + @Override + public Action[] getActions(boolean context) { + String path = element instanceof ControlFlowGraph ? CompilationViewTopComponent.ACTIONS_CFG : CompilationViewTopComponent.ACTIONS_INTERVALS; + return Lookups.forPath(path).lookupAll(Action.class).toArray(new Action[0]); + } + + @Override + public Action getPreferredAction() { + for (Action action : getActions(false)) { + if (action.isEnabled()) { + return action; + } + } + return null; + } + + } + + class CompilationNode extends CompilationElementNode { + public CompilationNode(Compilation compilation) { + super(compilation); + } + + @Override + public Image getIcon(int type) { + return ImageFolder; + } + + @Override + public Action[] getActions(boolean context) { + return Lookups.forPath(CompilationViewTopComponent.ACTIONS_COMPILATION).lookupAll(Action.class).toArray(new Action[0]); + } + + @Override + public Action getPreferredAction() { + return null; + } + + } + + class CompilationElementChildren extends Children.Keys { + private CompilationElement compilation; + + public CompilationElementChildren(CompilationElement compilation) { + this.compilation = compilation; + } + + public void doRefresh() { + addNotify(); + for (Node n : getNodes()) { + ((CompilationElementNode) n).doRefresh(); + } + } + + @Override + protected void addNotify() { + List elements = new ArrayList(); + for (CompilationElement element : compilation.getElements()) { + if (!filterCfg || element.getCompilation() != element.getParent() || !(element instanceof ControlFlowGraph) || ((ControlFlowGraph) element).hasHir() || ((ControlFlowGraph) element).hasLir()|| ((ControlFlowGraph) element).getNativeMethod() != null) { + elements.add(element); + } + } + setKeys(elements); + } + + protected Node[] createNodes(CompilationElement key) { + return new Node[]{new CompilationElementNode(key)}; + } + + } +} diff --git a/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/CompilationViewTopComponent.java b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/CompilationViewTopComponent.java new file mode 100644 index 000000000000..e96743d23bb9 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/CompilationViewTopComponent.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.compilation.view; + +import at.ssw.visualizer.compilation.view.action.OpenCompilationAction; +import at.ssw.visualizer.compilation.view.icons.Icons; +import at.ssw.visualizer.model.CompilationModel; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyVetoException; +import java.io.Serializable; +import java.util.Collection; +import javax.swing.Action; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JToggleButton; +import javax.swing.JToolBar; +import javax.swing.UIManager; +import javax.swing.border.Border; +import org.openide.awt.Toolbar; +import org.openide.awt.ToolbarPool; +import org.openide.explorer.ExplorerManager; +import org.openide.explorer.ExplorerUtils; +import org.openide.explorer.view.BeanTreeView; +import org.openide.nodes.Node; +import org.openide.util.ImageUtilities; +import org.openide.util.Lookup; +import org.openide.util.actions.Presenter; +import org.openide.util.lookup.Lookups; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; + +/** + * Explorer-like view of all compiled methods. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + */ +public final class CompilationViewTopComponent extends TopComponent implements ExplorerManager.Provider { + + public static final String ACTIONS_COMPILATION = "at-ssw-visualizer-actions-compilation"; + public static final String ACTIONS_CFG = "at-ssw-visualizer-actions-cfg"; + public static final String ACTIONS_INTERVALS = "at-ssw-visualizer-actions-intervals"; + protected ExplorerManager manager; + protected BeanTreeView view; + protected CompilationModelNode rootNode; + protected JToggleButton sortButton; + protected JToggleButton filterButton; + protected JToggleButton packageButton; + + private CompilationViewTopComponent() { + setName("Compiled Methods"); + setToolTipText("List of Compiled Methods"); + setIcon(ImageUtilities.loadImage(Icons.COMPILATIONS)); + + manager = new ExplorerManager(); + view = new BeanTreeView(); + view.setRootVisible(false); + + setLayout(new BorderLayout()); + add(createToolbar(), BorderLayout.NORTH); + add(view, BorderLayout.CENTER); + + CompilationModel model = Lookup.getDefault().lookup(CompilationModel.class); + rootNode = new CompilationModelNode(model); + manager.setRootContext(rootNode); + associateLookup(ExplorerUtils.createLookup(manager, getActionMap())); + + configButtonListener.actionPerformed(null); + } + + private Toolbar createToolbar() { + ToolbarPool.getDefault().setPreferredIconSize(16); + + Toolbar toolBar = new Toolbar(); + toolBar.setBorder((Border) UIManager.get("Nb.Editor.Toolbar.border")); + + toolBar.add(new OpenCompilationAction()); + toolBar.addSeparator(); + addElements(toolBar, Lookups.forPath(CompilationViewTopComponent.ACTIONS_COMPILATION).lookupAll(Action.class)); + toolBar.addSeparator(); + addElements(toolBar, Lookups.forPath(CompilationViewTopComponent.ACTIONS_CFG).lookupAll(Action.class)); + toolBar.addSeparator(); + addElements(toolBar, Lookups.forPath(CompilationViewTopComponent.ACTIONS_INTERVALS).lookupAll(Action.class)); + + toolBar.addSeparator(); + sortButton = new JToggleButton(new ImageIcon(ImageUtilities.loadImage(Icons.SORT)), false); + sortButton.setToolTipText("Sort List of Compilations"); + sortButton.addActionListener(configButtonListener); + toolBar.add(sortButton); + filterButton = new JToggleButton(new ImageIcon(ImageUtilities.loadImage(Icons.FILTER)), true); + filterButton.addActionListener(configButtonListener); + filterButton.setToolTipText("Filter Control Flow Graphs that have no IR (generated during bytecode parsing phase)"); + toolBar.add(filterButton); + packageButton = new JToggleButton(new ImageIcon(ImageUtilities.loadImage(Icons.PACKAGE)), false); + packageButton.addActionListener(configButtonListener); + packageButton.setToolTipText("Show Package Names"); + toolBar.add(packageButton); + toolBar.addSeparator(); + JButton collapseAllButton = new JButton(new ImageIcon(ImageUtilities.loadImage(Icons.COLLAPSE_ALL))); + collapseAllButton.addActionListener(collapseAllListener); + collapseAllButton.setToolTipText("Collapse All"); + toolBar.add(collapseAllButton); + + return toolBar; + } + + private void addElements(JToolBar toolBar, Collection actions) { + for (Action action : actions) { + if (action instanceof Presenter.Toolbar) { + toolBar.add(((Presenter.Toolbar) action).getToolbarPresenter()); + } else { + toolBar.add(action); + } + } + } + + @Override + public Dimension getMinimumSize() { + return new Dimension(0, 0); + } + + public ExplorerManager getExplorerManager() { + return manager; + } + private ActionListener configButtonListener = new ActionListener() { + + public void actionPerformed(ActionEvent event) { + org.openide.nodes.Node[] selectedNodes = manager.getSelectedNodes(); + rootNode.doRefresh(sortButton.isSelected(), filterButton.isSelected(), !packageButton.isSelected()); + try { + manager.setSelectedNodes(selectedNodes); + } catch (PropertyVetoException ex) { + throw new Error(ex); + } + } + }; + private ActionListener collapseAllListener = new ActionListener() { + + public void actionPerformed(ActionEvent event) { + for (Node n : rootNode.getChildren().getNodes()) { + view.collapseNode(n); + } + } + }; + // + private static final String PREFERRED_ID = "CompilationViewTopComponent"; + private static CompilationViewTopComponent instance; + + public static synchronized CompilationViewTopComponent getDefault() { + if (instance == null) { + instance = new CompilationViewTopComponent(); + } + return instance; + } + + public static synchronized CompilationViewTopComponent findInstance() { + return (CompilationViewTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID); + } + + @Override + public int getPersistenceType() { + return PERSISTENCE_ALWAYS; + } + + @Override + protected String preferredID() { + return PREFERRED_ID; + } + + @Override + public Object writeReplace() { + return new ResolvableHelper(); + } + + static final class ResolvableHelper implements Serializable { + + private static final long serialVersionUID = 1L; + + public Object readResolve() { + return CompilationViewTopComponent.getDefault(); + } + } + // +} diff --git a/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/ShowCompilationViewAction.java b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/ShowCompilationViewAction.java new file mode 100644 index 000000000000..aff1a42082a2 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/ShowCompilationViewAction.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.compilation.view; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.openide.windows.TopComponent; + +/** + * Action which shows Compilation component. + * + * @author Bernhard Stiftner + */ +public class ShowCompilationViewAction extends AbstractAction { + public ShowCompilationViewAction() { + super("Compiled Methods"); + } + + public void actionPerformed(ActionEvent event) { + TopComponent win = CompilationViewTopComponent.findInstance(); + win.open(); + win.requestActive(); + } +} diff --git a/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/OpenCompilationAction.java b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/OpenCompilationAction.java new file mode 100644 index 000000000000..204e4af25f40 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/OpenCompilationAction.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.compilation.view.action; + +import at.ssw.visualizer.compilation.view.icons.Icons; +import at.ssw.visualizer.model.CompilationModel; +import java.awt.event.ActionEvent; +import java.io.File; +import java.util.prefs.Preferences; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ImageIcon; +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileNameExtensionFilter; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.util.ImageUtilities; +import org.openide.util.Lookup; +import org.openide.util.RequestProcessor; +import org.openide.windows.WindowManager; + +/** + * Action opening a CFG file and loading it into the workspace. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + */ +public final class OpenCompilationAction extends AbstractAction { + public OpenCompilationAction() { + super("&Open Compiled Methods...", new ImageIcon(ImageUtilities.loadImage(Icons.OPEN))); + putValue(Action.SHORT_DESCRIPTION, "Open Compiled Methods"); + } + + public void actionPerformed(ActionEvent event) { + JFileChooser ch = new JFileChooser(); + ch.setCurrentDirectory(getLastDirectory()); + ch.setFileFilter(new FileNameExtensionFilter("Compiled methods (*.cfg)", "cfg")); + if (ch.showOpenDialog(WindowManager.getDefault().getMainWindow()) != JFileChooser.APPROVE_OPTION || ch.getSelectedFile() == null) { + return; + } + setLastDirectory(ch.getSelectedFile()); + + final String fileName = ch.getSelectedFile().getAbsolutePath(); + RequestProcessor.getDefault().post(new Runnable() { + public void run() { + CompilationModel model = Lookup.getDefault().lookup(CompilationModel.class); + String errorMsg = model.parseInputFile(fileName); + if (errorMsg != null) { + NotifyDescriptor d = new NotifyDescriptor.Message("Errors while parsing input:\n" + errorMsg, NotifyDescriptor.ERROR_MESSAGE); + DialogDisplayer.getDefault().notify(d); + } + } + }); + } + + private File getLastDirectory() { + Preferences prefs = Preferences.userNodeForPackage(getClass()); + String fileName = prefs.get("lastDirectory", null); + File file = null; + if (fileName != null) { + file = new File(fileName); + } + if (file == null || !file.exists()) { + file = new File(System.getProperty("user.home")); + } + return file; + } + + private void setLastDirectory(File file) { + Preferences prefs = Preferences.userNodeForPackage(getClass()); + if (!file.isDirectory()) { + file = file.getParentFile(); + } + prefs.put("lastDirectory", file.getPath()); + } +} diff --git a/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/RemoveAllCompilationsAction.java b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/RemoveAllCompilationsAction.java new file mode 100644 index 000000000000..1b8539e43697 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/RemoveAllCompilationsAction.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.compilation.view.action; + +import at.ssw.visualizer.compilation.view.icons.Icons; +import at.ssw.visualizer.model.CompilationModel; +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ImageIcon; +import org.openide.util.ImageUtilities; +import org.openide.util.Lookup; + +/** + * Action for removing all compilations from the workspace. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + */ +public final class RemoveAllCompilationsAction extends AbstractAction { + public RemoveAllCompilationsAction() { + super("Remove All Methods", new ImageIcon(ImageUtilities.loadImage(Icons.REMOVE_ALL))); + putValue(Action.SHORT_DESCRIPTION, "Remove All Methods"); + } + + public void actionPerformed(ActionEvent event) { + CompilationModel model = Lookup.getDefault().lookup(CompilationModel.class); + model.clear(); + } +} diff --git a/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/RemoveCompilationAction.java b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/RemoveCompilationAction.java new file mode 100644 index 000000000000..24c6c3f20f0a --- /dev/null +++ b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/RemoveCompilationAction.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.compilation.view.action; + +import at.ssw.visualizer.compilation.view.icons.Icons; +import at.ssw.visualizer.model.Compilation; +import org.openide.nodes.Node; +import org.openide.util.HelpCtx; +import org.openide.util.actions.CookieAction; + +/** + * Action for removing the currently selected compilation from the workspace. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + */ +public final class RemoveCompilationAction extends CookieAction { + protected void performAction(Node[] activatedNodes) { + Compilation compilation = activatedNodes[0].getLookup().lookup(Compilation.class); + compilation.getCompilationModel().removeCompilation(compilation); + } + + public String getName() { + return "Remove Method"; + } + + @Override + protected String iconResource() { + return Icons.REMOVE; + } + + protected int mode() { + return CookieAction.MODE_EXACTLY_ONE; + } + + protected Class[] cookieClasses() { + return new Class[]{Compilation.class}; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + protected boolean asynchronous() { + return false; + } +} diff --git a/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/icons/Icons.java b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/icons/Icons.java new file mode 100644 index 000000000000..ebcbd7cf05ff --- /dev/null +++ b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/icons/Icons.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.compilation.view.icons; + +/** + * + * @author Christian Wimmer + */ +public class Icons { + private static final String PATH = "at/ssw/visualizer/compilation/view/icons/"; + + public static final String OPEN = PATH + "open.gif"; + public static final String REMOVE = PATH + "remove.gif"; + public static final String REMOVE_ALL = PATH + "removeall.gif"; + + public static final String FOLDER = PATH + "folder.gif"; + public static final String COMPILATIONS = PATH + "compilations.gif"; + public static final String CFG = PATH + "cfg.gif"; + public static final String INTERVALS = PATH + "intervals.gif"; + + public static final String FILTER = PATH + "filter.gif"; + public static final String SORT = PATH + "sort.gif"; + public static final String PACKAGE = PATH + "package.gif"; + public static final String COLLAPSE_ALL = PATH + "collapseall.gif"; + + private Icons() { + } +} diff --git a/visualizer/C1Visualizer/CompilationView/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/CompilationView/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..fe26eac40b36 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationView/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.compilation.view +OpenIDE-Module-Layer: at/ssw/visualizer/compilation/view/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/compilation/view/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/Bundle.properties b/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/Bundle.properties new file mode 100644 index 000000000000..c6f874ec5fbb --- /dev/null +++ b/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Compilation View diff --git a/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/CompilationViewTopComponentSettings.xml b/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/CompilationViewTopComponentSettings.xml new file mode 100644 index 000000000000..0b20f777ba36 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/CompilationViewTopComponentSettings.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/CompilationViewTopComponentWstcref.xml b/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/CompilationViewTopComponentWstcref.xml new file mode 100644 index 000000000000..f725aff40389 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/CompilationViewTopComponentWstcref.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/cfg.gif b/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/cfg.gif new file mode 100644 index 0000000000000000000000000000000000000000..695e5a5cfa5b259193c4caa06228d1d38282d05e GIT binary patch literal 209 zcmZ?wbhEHb6krfwIKsfNc=^WsvX-qo52`hIShffGOip&56dgJ{D|B{t*o>^`dHKQ9 z)8iHt22PC%nHHV2B)M>1LCJ=~?3L*`D>JHAWG_6{_W%EX2HZgLCkrD3gBXJjND0VJ z23EfXs(mS$^D;O)x(+T0Q&0$Gl}%gly7`Mw^Bj+`f&eL2@7JQjYjwDt4moXp$h7ia m{sM2&e$N~&tK6u0rzb~*?R{FtsB7SAq7>|J^hShUjfWZCs($|cfA#bKkH7!F`St(Z@BiQa{{Qv=|DXRL zz<>l4f3h$#FmN;IfW$y%FtB(Pob+71*X+evXI>YLE;&}Fj8#mRE%&W?B30shyu13% zpT6C#3k-fJGjKF52@24V6I?%GvcZa|)%y<^9(-F=IB9W`k6g3(YLhfsMh0sDZC^x! literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/compilations.gif b/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/compilations.gif new file mode 100644 index 0000000000000000000000000000000000000000..983932fcccdf483e87b2c27e6d1b574edb8d4042 GIT binary patch literal 145 zcmV;C0B-+BNk%w1VGsZi0J8u9Z+3+G_PW7(ExmRt!+txlXeYmUIjdnLlT{y*Qz5No zFO*m*vTHJMI~f1}{{R30A^8LW000gEEC2ui01yBW000C&(8)<_1vX181xW;van2Ad zfiVIsvNTIFPQ@88vveqnqU4&(Bm@#!NCung$O0r@F(AW+J5GGPpEbzsdLsZkG>S40 literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/filter.gif b/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/filter.gif new file mode 100644 index 0000000000000000000000000000000000000000..6fe6f0e10a11203f4297dcdff4288ae6f2698fe7 GIT binary patch literal 219 zcmZ?wbhEHb6krfwIKseS;S!r)))JmlQ{FNwwRTEO>(Y?!&Aw5w|Ng%J`{~%WbLPKw)$;B Og`P}9k*1m=gEav5gIBTu literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/folder.gif b/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/folder.gif new file mode 100644 index 0000000000000000000000000000000000000000..51e703b1b9c671baa2be0f6525edb3b69403370d GIT binary patch literal 216 zcmZ?wbhEHb6krfwIKsg2wj(b5!P~sjn^4pBFDXFJ7TPNb)}zFdzZN zpDc_F4AKlbATf}g46LCK)cR5~=VfGBty!EmMJ+qTijl!k<;;`cOWYDXbj&=28jDUI znIPA;;-J&oD09KqYe6rnb~B%5E3$|ov_K2=INlov2DAUCeXWj``d;4+=ctxhW_>1 zr+yr*h9CRehX4QnA^8LW0018VEC2ui01yBW000G7;3tk`SvpdxN{;zSR4h}H3r%=F zJ?_FZj9@V0Di$x6q|$P6SvJt<#^UN^Fd9$R({Wum;4r6RERzg{w7Ot$6%O$Dq2L-I U6o`8QA#ec#fPn!7Wq}E%9Dt8G)5LOgQI2^@7e=H}5t7*jq~=si${U5|rU_+M-j`oOU#sb&i!)6! zQj1L2<%^C`(3Frs!zQH!M$LxHZR)0;HP3s_`~J#5py%WBe0&lT<%eQbQbx*H7)D;+ zC9mj8tL`~bsh6u?DXLzlp`Oc4!$r+QwcX=`U*-e?sC#^9(mL|ZR{74{H@%DrT- zV)#}6;rDanLRp{LGx3fOqC^RS3vNq|)X-(A$o!#sus3{C+!5o{Rj7~JHa zq?m$3AWMR=N;c$n#X>#8pgrH*sO=7AfY*vOt}fICx=Rq;>*}tJq5D zJOzs&S}52kF^hQm&roD!pv_wX{I_nC2C7b_o=HnlsqcF+ z>%ILpZc-}G=AK7a*@3|wd!lmkaw^a@Hhgq+cl5#IStlz|F}po7cvo=5fta}bq!M(A z4UvV+$@aw_y%1lD%Gl7ITxjV2!wCgP%26SkA9VL}>?w1J7eN*3kUXk(DEU6m(EdKjHN)jkv7c;{Jb4a3C!1>qfFng9R* literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/package.gif b/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/package.gif new file mode 100644 index 0000000000000000000000000000000000000000..b80590bb7be2f460f6d95e998a476d65c396b5dc GIT binary patch literal 256 zcmZ?wbhEHb6krfwIKseCWGhh>;5a!#gWSa7hJgOkal;~-;mI}eLP%7XTrwI0A2&5|X~k?% zSlH+>iHl2ZMZ&>DER%KBRWv#jnp!vom~I^DbXeHH=D^IsdduO!p+U$HZXWkH;5I}@JLW^VTq1sNF`2?+`R|Nmz| z1&Tje7#SG27<53wATtBYuHJj2L#xNM)vQ#JRl<>hDKU#-N&yE`p}?0M(Gw4T zcQCOfDoF5h^e6?|s2Q}nBw9^-rkP#C*lC&X!LwxVz&)al#c-qnqcDm}#WIy(BLW0V%N5`2M zYBi)fD)t0U*`nI(nzTRThYA-TS95cdU1Kv3A5Uwk5NlIM^h7?h?zV>Mb~Eg!3Nly& E026Ie7ytkO literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/sort.gif b/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/icons/sort.gif new file mode 100644 index 0000000000000000000000000000000000000000..6311cc00f80dadd6f5082e4381dc2bdaa28e6b66 GIT binary patch literal 153 zcmZ?wbhEHb6krfw*v!Dt=Qd?o<*NL$mekrQF|AAe`uB!(Z}yFfm2Rtco!n8ea?$_) z{~3^g;!hSv1_o{h9grBv3N_ARJ`qXtn-g`OJ{8w(; v#98&Xg*(Ax3QtReYl4Pg43pQg1got&3nw){NJ!P%?BU7qTUxJOkii-NY)mvJ literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/layer.xml b/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/layer.xml new file mode 100644 index 000000000000..c70bed252de8 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationView/src/main/resources/at/ssw/visualizer/compilation/view/layer.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/ControlFlowEditor/pom.xml b/visualizer/C1Visualizer/ControlFlowEditor/pom.xml new file mode 100644 index 000000000000..731714f625fe --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/pom.xml @@ -0,0 +1,139 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + ControlFlowEditor + 1.13-SNAPSHOT + nbm + ControlFlowEditor + + UTF-8 + + + + at.ssw.visualizer + VisualizerUI + ${project.version} + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + org.eclipse + draw2d + 3.2.100-v20070529 + + + + org.apache.xmlgraphics + batik-dom + ${batik.version} + + + org.apache.xmlgraphics + batik-svggen + ${batik.version} + + + + org.netbeans.api + org-netbeans-api-visual + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-options-api + ${netbeans.version} + + + org.netbeans.api + org-openide-actions + ${netbeans.version} + + + org.netbeans.api + org-openide-awt + ${netbeans.version} + + + org.netbeans.api + org-openide-dialogs + ${netbeans.version} + + + org.netbeans.api + org-openide-loaders + ${netbeans.version} + + + org.netbeans.api + org-openide-nodes + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-util-ui + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.ControlFlowEditor + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/CfgEditorContext.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/CfgEditorContext.java new file mode 100644 index 000000000000..ced6077b8069 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/CfgEditorContext.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package at.ssw.visualizer.cfg; + +public abstract class CfgEditorContext { + + public static final int LAYOUT_HIERARCHICALNODELAYOUT = 1; + public static final int LAYOUT_HIERARCHICALCOMPOUNDLAYOUT = 2; + + public static final int ROUTING_DIRECTLINES = 1; + public static final int ROUTING_BEZIER = 2; + + public static final int MAX_AUTOEDGESVISIBLE = 8; +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/AbstractCfgEditorAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/AbstractCfgEditorAction.java new file mode 100644 index 000000000000..3a7306d0b2a7 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/AbstractCfgEditorAction.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + + +import at.ssw.visualizer.cfg.graph.CfgEventListener; +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import at.ssw.visualizer.cfg.graph.CfgScene; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import org.openide.util.actions.CallableSystemAction; +import org.openide.windows.TopComponent; + +/** + * The common superclass of all concrete actions related to the CFG visualizer. + * + * @author Bernhard Stiftner + * @author Rumpfhuber Stefan + */ +public abstract class AbstractCfgEditorAction extends CallableSystemAction implements CfgEventListener , PropertyChangeListener { + + CfgEditorTopComponent topComponent = null; + + public AbstractCfgEditorAction() { + TopComponent.getRegistry().addPropertyChangeListener(this); + setEnabled(false); + + } + + protected CfgEditorTopComponent getEditor() { + return topComponent; + } + + protected void setEditor(CfgEditorTopComponent newTopComponent) { + CfgEditorTopComponent oldTopComponent = getEditor(); + if(newTopComponent != oldTopComponent){ + if(oldTopComponent != null) { + oldTopComponent.getCfgScene().removeCfgEventListener(this); + } + this.topComponent = newTopComponent; + if (newTopComponent != null) { + newTopComponent.getCfgScene().addCfgEventListener(this); + selectionChanged(newTopComponent.getCfgScene()); + } + this.setEnabled(newTopComponent!=null); + } + } + + + @Override + public JMenuItem getMenuPresenter() { + return new JMenuItem(this); + } + + + @Override + public JComponent getToolbarPresenter() { + JButton b = new JButton(this); + if (getIcon() != null) { + b.setText(null); + b.setToolTipText(getName()); + } + return b; + } + + + @Override + public JMenuItem getPopupPresenter() { + return new JMenuItem(this); + } + + + @Override + protected boolean asynchronous() { + return false; + } + + + public void propertyChange(PropertyChangeEvent e) { + if ( e.getPropertyName().equals(TopComponent.Registry.PROP_ACTIVATED)) { + if(e.getNewValue() instanceof CfgEditorTopComponent){ + CfgEditorTopComponent tc = (CfgEditorTopComponent)e.getNewValue(); + setEditor(tc); + selectionChanged(tc.getCfgScene()); + } + } + } + + + public void selectionChanged(CfgScene scene) { + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/AbstractRouterAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/AbstractRouterAction.java new file mode 100644 index 000000000000..e5fa947df6be --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/AbstractRouterAction.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import javax.swing.JRadioButtonMenuItem; +import javax.swing.JToggleButton; +import org.openide.util.actions.Presenter; + +/** + * Common superclass for all actions which set the link router. + * + * @author Bernhard Stiftner + * @author Rumpfhuber Stefan + */ +public abstract class AbstractRouterAction extends AbstractCfgEditorAction implements Presenter.Menu, Presenter.Popup, Presenter.Toolbar { + + public void performAction() { + CfgEditorTopComponent tc = getEditor(); + if (tc != null) { + setLinkRouter(tc); + } + } + + protected abstract void setLinkRouter(CfgEditorTopComponent editor); + + @Override + public JMenuItem getMenuPresenter() { + JMenuItem presenter = new MenuPresenter(); + presenter.setToolTipText(getName()); + return presenter; + } + + @Override + public JMenuItem getPopupPresenter() { + return getMenuPresenter(); + } + + @Override + public JComponent getToolbarPresenter() { + ToolbarPresenter presenter = new ToolbarPresenter(); + presenter.setToolTipText(getName()); + return presenter; + } + + class MenuPresenter extends JRadioButtonMenuItem { + + public MenuPresenter() { + super(AbstractRouterAction.this); + setIcon(null); + } + } + + class ToolbarPresenter extends JToggleButton { + + public ToolbarPresenter() { + super(AbstractRouterAction.this); + setText(null); + } + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ColorAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ColorAction.java new file mode 100644 index 000000000000..a2126791ca0f --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ColorAction.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + +import at.ssw.visualizer.cfg.graph.CfgEventListener; +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import at.ssw.visualizer.cfg.graph.CfgScene; +import at.ssw.visualizer.cfg.model.CfgNode; +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.image.BufferedImage; +import java.util.Set; +import javax.swing.AbstractAction; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.SwingConstants; +import javax.swing.plaf.basic.BasicArrowButton; +import org.openide.util.HelpCtx; +import org.openide.util.actions.Presenter; + +/** + * Changes the background color of a node. + * + * @author Bernhard Stiftner + * @author Rumpfhuber Stefan + */ +public class ColorAction extends AbstractCfgEditorAction implements Presenter.Menu, Presenter.Popup, Presenter.Toolbar { + + public static final int IMAGE_WIDTH = 16; + public static final int IMAGE_HEIGHT = 16; + + + /** Names of colors shown in color select lists. */ + private static final String[] COLOR_NAMES = {"White", "Light Gray", "Dark Gray", "Light Yellow", "Dark Yellow", "Light Green", "Dark Green", "Light Cyan", "Dark Cyan", "Light Blue", "Dark Blue", "Light Magenta", "Dark Magenta", "Light Red", "Dark Red"}; + + /** Values of colors shown in color select lists. */ + private static final Color[] COLORS = { + new Color(0xFFFFFF), new Color(0xD4D0C8), new Color(0xA4A098), new Color(0xF0F0B0), new Color(0xE0E040), + new Color(0xB0F0B0), new Color(0x40E040), new Color(0xB0F0F0), new Color(0x40E0E0), new Color(0xB0B0F0), + new Color(0x4040E0), new Color(0xF0B0F0), new Color(0xE040E0), new Color(0xF0B0B0), new Color(0xE04040) + }; + + public void performAction() { + // nothing to do here, the presenters are supposed to call + // performAction(Color) + } + + protected void performAction(Color color) { + CfgEditorTopComponent tc = getEditor(); + if (tc != null) { + tc.getCfgScene().setSelectedNodesColor(color); + } + } + + public String getName() { + return "Change NodeColor"; + } + + @Override + protected String iconResource() { + return "at/ssw/visualizer/cfg/icons/color.gif"; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public JMenuItem getMenuPresenter() { + return new MenuPresenter(); + } + + @Override + public JMenuItem getPopupPresenter() { + return new MenuPresenter(); + } + + @Override + public JComponent getToolbarPresenter() { + return new ToolbarPresenter(); + } + + class MenuPresenter extends JMenu { + + public MenuPresenter() { + super(ColorAction.this); + initGUI(); + } + + protected void initGUI() { + CfgEditorTopComponent tc = getEditor(); + if( tc != null && tc.getCfgScene().getSelectedNodes().size()==0) { + //no node selected + setEnabled(false); + } else { + add(new SetColorAction(null, "Automatic")); + for (int i = 0; i < COLORS.length; i++) { + add(new SetColorAction(COLORS[i], COLOR_NAMES[i])); + } + } + } + } + + class ToolbarPresenter extends JButton implements CfgEventListener, MouseListener { + + final int arrowSize = 5; + final int arrowMargin = 3; + JPopupMenu popup; + + public ToolbarPresenter() { + setIcon(createIcon()); + setToolTipText(ColorAction.this.getName()); + + popup = new JPopupMenu(); + popup.add(new SetColorAction(null, "Automatic")); + for (int i = 0; i < COLORS.length; i++) { + popup.add(new SetColorAction(COLORS[i], COLOR_NAMES[i])); + } + addMouseListener(this); + } + + public Icon createIcon() { + BasicArrowButton arrow = new BasicArrowButton(SwingConstants.SOUTH); + BufferedImage img = new BufferedImage(IMAGE_WIDTH + arrowSize + 2 * arrowMargin, IMAGE_HEIGHT, BufferedImage.TYPE_INT_ARGB); + Graphics g = img.getGraphics(); + ColorAction.this.getIcon().paintIcon(this, g, 0, 0); + arrow.paintTriangle(g, IMAGE_WIDTH + arrowMargin + arrowSize / 2, IMAGE_HEIGHT / 2 - arrowSize / 2, arrowSize, SwingConstants.SOUTH, true); + return new ImageIcon(img); + } + + + public void selectionChanged(CfgScene scene) { + Set nodes = scene.getSelectedNodes(); + setEnabled(nodes.size() > 0); + } + + public void mouseClicked(MouseEvent e) { + if (e.getX() < getInsets().left + IMAGE_WIDTH + arrowMargin) { + performAction(null); + } else { + popup.show(this, 0, getSize().height); + } + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + public void mousePressed(MouseEvent e) { + } + + public void mouseReleased(MouseEvent e) { + } + } + + protected Icon createIcon(Color color) { + if (color == null) { + return ColorAction.this.getIcon(); + } else { + return new ColorIcon(color); + } + } + + class ColorIcon implements Icon { + + Color color; + + public ColorIcon(Color color) { + this.color = color; + } + + public int getIconWidth() { + return IMAGE_WIDTH; + } + + public int getIconHeight() { + return IMAGE_HEIGHT; + } + + public void paintIcon(Component c, Graphics g, int x, int y) { + Color oldColor = g.getColor(); + g.setColor(color); + g.fillRect(x, y, IMAGE_WIDTH, IMAGE_HEIGHT); + g.setColor(oldColor); + } + } + + class SetColorAction extends AbstractAction { + + Color color; + String name; + Icon icon; + + public SetColorAction(Color color, String name) { + super(name, createIcon(color)); + this.color = color; + this.name = name; + icon = (Icon) getValue(AbstractAction.SMALL_ICON); + } + + public Color getColor() { + return color; + } + + public String getName() { + return name; + } + + public Icon getIcon() { + return icon; + } + + public void actionPerformed(ActionEvent e) { + performAction(color); + } + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ExportAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ExportAction.java new file mode 100644 index 000000000000..c363364f3c14 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ExportAction.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import at.ssw.visualizer.cfg.graph.CfgScene; +import java.io.File; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileNameExtensionFilter; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.util.HelpCtx; +import org.apache.batik.dom.GenericDOMImplementation; +import org.apache.batik.svggen.SVGGeneratorContext; +import org.apache.batik.svggen.SVGGraphics2D; +import org.w3c.dom.DOMImplementation; +import java.io.*; +import java.awt.Graphics2D; + +/** + * Exports Scene to various Graphics Formats + * + * @author Rumpfhuber Stefan + */ +public class ExportAction extends AbstractCfgEditorAction { + + public static final String DESCRIPTION_SVG = "Scaleable Vector Format (.svg)"; + public static final String EXT_SVG = "svg"; + + String lastDirectory = null; + + @Override + public void performAction() { + CfgEditorTopComponent tc = this.getEditor(); + CfgScene scene = tc.getCfgScene(); + JComponent view = scene.getView(); + + JFileChooser chooser = new JFileChooser (); + chooser.setAcceptAllFileFilterUsed(false); + chooser.setDialogTitle (getName()); + chooser.setDialogType (JFileChooser.SAVE_DIALOG); + chooser.setMultiSelectionEnabled (false); + chooser.setFileSelectionMode (JFileChooser.FILES_ONLY); + chooser.addChoosableFileFilter(new FileNameExtensionFilter(DESCRIPTION_SVG, EXT_SVG)); + if(lastDirectory != null) + chooser.setCurrentDirectory(new File(lastDirectory)); + chooser.setSelectedFile(new File(tc.getName())); + + + if (chooser.showSaveDialog (tc) != JFileChooser.APPROVE_OPTION) + return; + + File file = chooser.getSelectedFile (); + + if(file == null) + return; + + FileNameExtensionFilter filter = (FileNameExtensionFilter) chooser.getFileFilter(); + String fn = file.getAbsolutePath().toLowerCase(); + String ext = filter.getExtensions()[0]; + if(!fn.endsWith("." + ext)){ + file = new File( file.getParentFile(), file.getName() + "." + ext); + } + + if (file.exists ()) { + DialogDescriptor descriptor = new DialogDescriptor ( + "File (" + file.getAbsolutePath () + ") already exists. Do you want to overwrite it?", + "File Exists", true, DialogDescriptor.YES_NO_OPTION, DialogDescriptor.NO_OPTION, null); + DialogDisplayer.getDefault ().createDialog (descriptor).setVisible (true); + if (descriptor.getValue () != DialogDescriptor.YES_OPTION) + return; + } + + lastDirectory = chooser.getCurrentDirectory().getAbsolutePath(); + + if(ext.equals(EXT_SVG)){ + DOMImplementation dom = GenericDOMImplementation.getDOMImplementation(); + org.w3c.dom.Document document = dom.createDocument("http://www.w3.org/2000/svg", "svg", null); + SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(document); + ctx.setEmbeddedFontsOn(true); + Graphics2D svgGenerator = new SVGGraphics2D(ctx, true); + scene.paint(svgGenerator); + FileOutputStream os = null; + try { + os = new FileOutputStream(file); + Writer out = new OutputStreamWriter(os, "UTF-8"); + assert svgGenerator instanceof SVGGraphics2D; + SVGGraphics2D svgGraphics = (SVGGraphics2D)svgGenerator; + svgGraphics.stream(out, true); + } catch (IOException e) { + // NotifyDescriptor message = new NotifyDescriptor.Message( + // Bundle.EXPORT_BATIK_ErrorExportingSVG(e.getLocalizedMessage()), NotifyDescriptor.ERROR_MESSAGE); + // DialogDisplayer.getDefault().notifyLater(message); + } finally { + if (os != null) { + try { + os.close(); + } catch (IOException e) { + } + } + } + } + } + + + @Override + public String getName() { + return "Export CFG"; + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + protected String iconResource() { + return "at/ssw/visualizer/cfg/icons/disk.gif"; + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/HideEdgesAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/HideEdgesAction.java new file mode 100644 index 000000000000..253e9166cba0 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/HideEdgesAction.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + + +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import at.ssw.visualizer.cfg.graph.CfgScene; +import at.ssw.visualizer.cfg.graph.EdgeWidget; +import at.ssw.visualizer.cfg.model.CfgEdge; +import at.ssw.visualizer.cfg.model.CfgNode; +import org.openide.util.HelpCtx; + +/** + * Hides all edges connected to the selected node. + * + * @author Bernhard Stiftner + * @author Rumpfhuber Stefan + */ +public class HideEdgesAction extends AbstractCfgEditorAction { + + public void performAction() { + CfgEditorTopComponent tc = getEditor(); + if (tc != null) { + tc.getCfgScene().setSelectedEdgesVisibility(false); + } + } + + public String getName() { + return "Hide Edges"; + } + + @Override + protected String iconResource() { + return "at/ssw/visualizer/cfg/icons/hideedges.gif"; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public void selectionChanged(CfgScene scene) { + for (CfgNode n : scene.getSelectedNodes()) { + for (CfgEdge e : scene.findNodeEdges(n, true, true) ){ + EdgeWidget ew = (EdgeWidget) scene.findWidget(e); + if(ew.isVisible()) { + setEnabled(true); + return; + } + } + } + setEnabled(false); + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/HierarchicalCompoundLayoutAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/HierarchicalCompoundLayoutAction.java new file mode 100644 index 000000000000..11cc54ed7219 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/HierarchicalCompoundLayoutAction.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + +import at.ssw.visualizer.cfg.CfgEditorContext; +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import at.ssw.visualizer.cfg.graph.CfgScene; +import org.openide.util.HelpCtx; + + +public class HierarchicalCompoundLayoutAction extends AbstractCfgEditorAction { + + public void performAction() { + CfgEditorTopComponent tc = getEditor(); + if (tc != null) { + CfgScene scene = tc.getCfgScene(); + scene.setSceneLayout(CfgEditorContext.LAYOUT_HIERARCHICALCOMPOUNDLAYOUT); + scene.applyLayout(); + } + } + + public String getName() { + return "Hierarchical Compound Layout"; + } + + + @Override + protected String iconResource() { + return "at/ssw/visualizer/cfg/icons/arrangeloop.gif"; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/HierarchicalNodeLayoutAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/HierarchicalNodeLayoutAction.java new file mode 100644 index 000000000000..a6c7563fdb4c --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/HierarchicalNodeLayoutAction.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + +import at.ssw.visualizer.cfg.CfgEditorContext; +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import at.ssw.visualizer.cfg.graph.CfgScene; +import org.openide.util.HelpCtx; + + +public class HierarchicalNodeLayoutAction extends AbstractCfgEditorAction { + + @Override + public void performAction() { + CfgEditorTopComponent tc = getEditor(); + if (tc != null) { + CfgScene scene = tc.getCfgScene(); + scene.setSceneLayout(CfgEditorContext.LAYOUT_HIERARCHICALNODELAYOUT); + scene.applyLayout(); + } + } + + public String getName() { + return "Hierarchical Node Layout"; + } + + + @Override + protected String iconResource() { + return "at/ssw/visualizer/cfg/icons/arrangehier.gif"; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ShowAllAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ShowAllAction.java new file mode 100644 index 000000000000..fdae5ce5c843 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ShowAllAction.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import at.ssw.visualizer.cfg.graph.CfgScene; +import org.openide.util.HelpCtx; + + +/** + * Adjusts the Zoom factor of the Scene to the bounds of Scroll panel + * to get a clean view on the whole graph. + * + */ +public class ShowAllAction extends AbstractCfgEditorAction { + + @Override + public void performAction() { + CfgEditorTopComponent tc = getEditor(); + if (tc != null) { + CfgScene scene = tc.getCfgScene(); + scene.zoomScene(); + + } + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public String getName() { + return "Fit Scene to Window"; + } + + @Override + protected String iconResource() { + return "at/ssw/visualizer/cfg/icons/autosize.gif"; + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ShowCFGEditorAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ShowCFGEditorAction.java new file mode 100644 index 000000000000..3e85928871a7 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ShowCFGEditorAction.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + +import at.ssw.visualizer.cfg.editor.CfgEditorSupport; +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import at.ssw.visualizer.cfg.icons.Icons; +import at.ssw.visualizer.core.focus.Focus; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import org.openide.nodes.Node; +import org.openide.util.HelpCtx; +import org.openide.util.actions.CookieAction; + +/** + * Shows the CFG visualizer for the currently selected compilation. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + */ +public final class ShowCFGEditorAction extends CookieAction { + + protected void performAction(Node[] activatedNodes) { + ControlFlowGraph cfg = activatedNodes[0].getLookup().lookup(ControlFlowGraph.class); + if (!Focus.findEditor(CfgEditorTopComponent.class, cfg)) { + CfgEditorSupport editor = new CfgEditorSupport(cfg); + editor.open(); + } + } + + @Override + protected boolean enable(Node[] activatedNodes) { + if (!super.enable(activatedNodes)) { + return false; + } + ControlFlowGraph cfg = activatedNodes[0].getLookup().lookup(ControlFlowGraph.class); + return cfg.getBasicBlocks().size() > 0; + } + + public String getName() { + return "Open Control Flow Graph"; + } + + @Override + protected String iconResource() { + return Icons.CFG; + } + + protected int mode() { + return CookieAction.MODE_EXACTLY_ONE; + } + + protected Class[] cookieClasses() { + return new Class[]{ControlFlowGraph.class}; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + protected boolean asynchronous() { + return false; + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ShowEdgesAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ShowEdgesAction.java new file mode 100644 index 000000000000..f0c2c7d89212 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ShowEdgesAction.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import at.ssw.visualizer.cfg.graph.CfgScene; +import at.ssw.visualizer.cfg.graph.EdgeWidget; +import at.ssw.visualizer.cfg.model.CfgEdge; +import at.ssw.visualizer.cfg.model.CfgNode; +import org.openide.util.HelpCtx; + +/** + * Shows all edges connected to the selected node. + * + * @author Bernhard Stiftner + * @author Rumpfhuber Stefan + */ +public class ShowEdgesAction extends AbstractCfgEditorAction { + + public void performAction() { + CfgEditorTopComponent tc = getEditor(); + if (tc != null) { + tc.getCfgScene().setSelectedEdgesVisibility(true); + } + } + + public String getName() { + return "Show edges"; + } + + + @Override + protected String iconResource() { + return "at/ssw/visualizer/cfg/icons/showedges.gif"; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public void selectionChanged(CfgScene scene) { + for (CfgNode n : scene.getSelectedNodes()) { + for (CfgEdge e : scene.findNodeEdges(n, true, true) ){ + EdgeWidget ew = (EdgeWidget) scene.findWidget(e); + if(!ew.isVisible()) { + setEnabled(true); + return; + } + } + } + setEnabled(false); + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/SwitchLoopClustersAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/SwitchLoopClustersAction.java new file mode 100644 index 000000000000..9a2f7f23019b --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/SwitchLoopClustersAction.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import at.ssw.visualizer.cfg.graph.CfgScene; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import javax.swing.JComponent; +import javax.swing.JToggleButton; +import org.openide.util.HelpCtx; +import org.openide.util.actions.Presenter; + +public class SwitchLoopClustersAction extends AbstractCfgEditorAction implements Presenter.Toolbar { + + @Override + public void performAction() { + CfgEditorTopComponent tc = getEditor(); + if (tc != null) { + CfgScene scene = tc.getCfgScene(); + boolean visible = scene.isLoopClusterVisible(); + scene.setLoopWidgets(!visible); + } + } + + @Override + public String getName() { + return "Enable/Disable Loop Clusters"; + } + + @Override + protected String iconResource() { + return "at/ssw/visualizer/cfg/icons/cluster.gif"; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + + @Override + public JComponent getToolbarPresenter() { + return new ToolbarPresenter(); + } + + class ToolbarPresenter extends JToggleButton { + private static final String TOOLTIP_ENABLE = "Enable LoopClusters"; + private static final String TOOLTIP_DISABLE = "Disable LoopClusters"; + + public ToolbarPresenter() { + super(SwitchLoopClustersAction.this); + setText(null); + this.setToolTipText(TOOLTIP_DISABLE); + this.setSelected(true); + + this.addItemListener(new ItemListener(){ + public void itemStateChanged(ItemEvent e) { + if(isSelected()){ + setToolTipText(TOOLTIP_DISABLE); + } else { + setToolTipText(TOOLTIP_ENABLE); + } + } + }); + } + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/UseBezierRouterAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/UseBezierRouterAction.java new file mode 100644 index 000000000000..67317a526af3 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/UseBezierRouterAction.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + +import at.ssw.visualizer.cfg.CfgEditorContext; +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import org.openide.util.HelpCtx; + + +public class UseBezierRouterAction extends AbstractRouterAction { + + @Override + protected void setLinkRouter(CfgEditorTopComponent editor) { + editor.getCfgScene().setRouter(CfgEditorContext.ROUTING_BEZIER); + } + + @Override + public String getName() { + return "Use Bezier Router"; + } + + @Override + protected String iconResource() { + return "at/ssw/visualizer/cfg/icons/bezierrouter.gif"; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/UseDirectLineRouterAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/UseDirectLineRouterAction.java new file mode 100644 index 000000000000..220b5ee1ee5e --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/UseDirectLineRouterAction.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + +import at.ssw.visualizer.cfg.CfgEditorContext; +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import org.openide.util.HelpCtx; + + +public class UseDirectLineRouterAction extends AbstractRouterAction { + + @Override + protected void setLinkRouter(CfgEditorTopComponent editor) { + editor.getCfgScene().setRouter(CfgEditorContext.ROUTING_DIRECTLINES); + } + + @Override + public String getName() { + return "User Direct Router"; + } + + @Override + protected String iconResource() { + return "at/ssw/visualizer/cfg/icons/fanrouter.gif"; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ZoominAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ZoominAction.java new file mode 100644 index 000000000000..3d01c5389328 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ZoominAction.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import at.ssw.visualizer.cfg.graph.CfgScene; +import org.openide.util.HelpCtx; + + +public class ZoominAction extends AbstractCfgEditorAction { + + @Override + public void performAction() { + CfgEditorTopComponent tc = getEditor(); + if (tc != null) { + CfgScene scene = tc.getCfgScene(); + scene.animateZoom(1.1); + } + } + + @Override + protected String iconResource() { + return "at/ssw/visualizer/cfg/icons/zoomin.gif"; + } + + @Override + public String getName() { + return "Zoomin"; + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ZoomoutAction.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ZoomoutAction.java new file mode 100644 index 000000000000..fbfd30f5c591 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/action/ZoomoutAction.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.action; + +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import at.ssw.visualizer.cfg.graph.CfgScene; +import org.openide.util.HelpCtx; + +public class ZoomoutAction extends AbstractCfgEditorAction { + + @Override + public void performAction() { + CfgEditorTopComponent tc = getEditor(); + if (tc != null) { + CfgScene scene = tc.getCfgScene(); + scene.animateZoom(0.9); + } + } + + @Override + protected String iconResource() { + return "at/ssw/visualizer/cfg/icons/zoomout.gif"; + } + + @Override + public String getName() { + return "Zoomout"; + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/editor/CfgEditorSupport.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/editor/CfgEditorSupport.java new file mode 100644 index 000000000000..9231210a56ec --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/editor/CfgEditorSupport.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.editor; + +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.beans.VetoableChangeListener; +import java.beans.VetoableChangeSupport; +import java.io.IOException; +import org.openide.cookies.OpenCookie; +import org.openide.windows.CloneableOpenSupport; +import org.openide.windows.CloneableTopComponent; + + +public class CfgEditorSupport extends CloneableOpenSupport implements OpenCookie { + private ControlFlowGraph cfg; + + public CfgEditorSupport(ControlFlowGraph cfg) { + super(new Env()); + ((Env) env).editorSupport = this; + this.cfg = cfg; + } + + protected CloneableTopComponent createCloneableTopComponent() { + return new CfgEditorTopComponent(cfg); + } + + public String messageOpened() { + return "Opened " + cfg.getCompilation().getMethod() + " - " + cfg.getName(); + } + + public String messageOpening() { + return "Opening " + cfg.getCompilation().getMethod() + " - " + cfg.getName(); + } + + + public static class Env implements CloneableOpenSupport.Env { + private PropertyChangeSupport prop = new PropertyChangeSupport(this); + private VetoableChangeSupport veto = new VetoableChangeSupport(this); + private CfgEditorSupport editorSupport; + + public boolean isValid() { + return true; + } + + public boolean isModified() { + return false; + } + + public void markModified() throws IOException { + throw new IOException("Editor is readonly"); + } + + public void unmarkModified() { + // Nothing to do. + } + + public CloneableOpenSupport findCloneableOpenSupport() { + return editorSupport; + } + + public void addPropertyChangeListener(PropertyChangeListener l) { + prop.addPropertyChangeListener(l); + } + + public void removePropertyChangeListener(PropertyChangeListener l) { + prop.removePropertyChangeListener(l); + } + + public void addVetoableChangeListener(VetoableChangeListener l) { + veto.addVetoableChangeListener(l); + } + + public void removeVetoableChangeListener(VetoableChangeListener l) { + veto.removeVetoableChangeListener(l); + } + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/editor/CfgEditorTopComponent.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/editor/CfgEditorTopComponent.java new file mode 100644 index 000000000000..1974ded48f1d --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/editor/CfgEditorTopComponent.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.editor; + +import at.ssw.visualizer.cfg.action.ShowAllAction; +import at.ssw.visualizer.cfg.action.ColorAction; +import at.ssw.visualizer.cfg.action.ExportAction; +import at.ssw.visualizer.cfg.action.HideEdgesAction; +import at.ssw.visualizer.cfg.action.HierarchicalCompoundLayoutAction; +import at.ssw.visualizer.cfg.action.HierarchicalNodeLayoutAction; +import at.ssw.visualizer.cfg.action.ShowEdgesAction; +import at.ssw.visualizer.cfg.action.SwitchLoopClustersAction; +import at.ssw.visualizer.cfg.action.UseBezierRouterAction; +import at.ssw.visualizer.cfg.action.UseDirectLineRouterAction; +import at.ssw.visualizer.cfg.action.ZoominAction; +import at.ssw.visualizer.cfg.action.ZoomoutAction; +import at.ssw.visualizer.cfg.graph.CfgEventListener; +import at.ssw.visualizer.cfg.graph.CfgScene; +import at.ssw.visualizer.cfg.graph.EdgeWidget; +import at.ssw.visualizer.cfg.graph.NodeWidget; +import at.ssw.visualizer.cfg.model.CfgEdge; +import at.ssw.visualizer.cfg.model.CfgNode; +import at.ssw.visualizer.cfg.preferences.CfgPreferences; +import at.ssw.visualizer.cfg.preferences.FlagsSetting; +import javax.swing.ScrollPaneConstants; +import at.ssw.visualizer.core.selection.Selection; +import at.ssw.visualizer.core.selection.SelectionManager; +import at.ssw.visualizer.core.selection.SelectionProvider; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import java.awt.BorderLayout; +import java.awt.Color; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JComponent; +import javax.swing.JScrollPane; +import javax.swing.JToggleButton; +import javax.swing.UIManager; +import javax.swing.border.Border; +import org.netbeans.api.visual.widget.Widget; +import org.openide.awt.Toolbar; +import org.openide.util.ImageUtilities; +import org.openide.windows.CloneableTopComponent; +import org.openide.windows.TopComponent; +import org.openide.util.actions.SystemAction; + +public class CfgEditorTopComponent extends CloneableTopComponent implements PropertyChangeListener, SelectionProvider { + + private CfgScene scene; + private JScrollPane jScrollPane; + private ControlFlowGraph cfg; + private JComponent myView; + private Selection selection; + + public CfgEditorTopComponent(ControlFlowGraph cfg) { + this.cfg = cfg; + + setIcon(ImageUtilities.loadImage("at/ssw/visualizer/cfg/icons/cfg.gif")); + setName(cfg.getParent().getShortName()); + setToolTipText(cfg.getCompilation().getMethod() + " - " + cfg.getName()); + + //panel setup + this.jScrollPane = new JScrollPane(); + this.jScrollPane.setOpaque(true); + this.jScrollPane.setBorder(BorderFactory.createEmptyBorder()); + this.jScrollPane.setViewportBorder(BorderFactory.createEmptyBorder()); + this.scene = new CfgScene(this); + this.myView = scene.createView(); + this.jScrollPane.setViewportView(myView); + this.setLayout(new BorderLayout()); + this.add(createToolbar(), BorderLayout.NORTH); + this.add(jScrollPane, BorderLayout.CENTER); + jScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + jScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); + jScrollPane.getVerticalScrollBar().setEnabled(true); + jScrollPane.getHorizontalScrollBar().setEnabled(true); + + //setup enviroment,register listeners + selection = new Selection(); + selection.put(cfg); + selection.put(scene); + selection.addChangeListener(scene); + + scene.validate(); + scene.applyLayout(); + } + + public Selection getSelection() { + return selection; + } + + public ControlFlowGraph getCfg() { + return cfg; + } + + public JScrollPane getJScrollPanel() { + return jScrollPane; + } + + public CfgScene getCfgScene() { + return scene; + } + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_NEVER; + } + + @Override + protected void componentOpened() { + super.componentOpened(); + CfgPreferences.getInstance().addPropertyChangeListener(this); + } + + @Override + protected void componentActivated() { + super.componentActivated(); + SelectionManager.getDefault().setSelection(selection); + this.getCfgScene().updateGlobalSelection(); + this.getCfgScene().fireSelectionChanged(); + } + + @Override + protected void componentClosed() { + super.componentClosed(); + SelectionManager.getDefault().removeSelection(selection); + CfgPreferences.getInstance().removePropertyChangeListener(this); + } + + @Override + protected CloneableTopComponent createClonedObject() { + CfgEditorTopComponent component = new CfgEditorTopComponent(cfg); + component.setActivatedNodes(getActivatedNodes()); + return component; + } + + public void propertyChange(PropertyChangeEvent evt) { + if (this.scene != null) { + + String propName = evt.getPropertyName(); + CfgPreferences prefs = CfgPreferences.getInstance(); + if (propName.equals(CfgPreferences.PROP_BACKGROUND_COLOR)) { + scene.setBackground(prefs.getBackgroundColor()); + scene.revalidate(); + } else if (propName.equals(CfgPreferences.PROP_NODE_COLOR)) { + for (NodeWidget nw : scene.getNodeWidgets()) { + //only change the node color if its not a custom color + if (!nw.isNodeColorCustomized()) { + nw.setNodeColor(prefs.getNodeColor(), false); + } + } + } else if (propName.equals(CfgPreferences.PROP_EDGE_COLOR)) { + for (CfgEdge e : scene.getEdges()) { + if (!e.isBackEdge() && !e.isXhandler()) { + EdgeWidget w = (EdgeWidget) scene.findWidget(e); + w.setLineColor(prefs.getEdgeColor()); + } + } + } else if (propName.equals(CfgPreferences.PROP_BACK_EDGE_COLOR)) { + for (CfgEdge e : scene.getEdges()) { + if (e.isBackEdge()) { + EdgeWidget w = (EdgeWidget) scene.findWidget(e); + w.setLineColor(prefs.getBackedgeColor()); + } + } + } else if (propName.equals(CfgPreferences.PROP_EXCEPTION_EDGE_COLOR)) { + for (CfgEdge e : scene.getEdges()) { + if (e.isXhandler()) { + EdgeWidget w = (EdgeWidget) scene.findWidget(e); + w.setLineColor(prefs.getExceptionEdgeColor()); + } + } + } else if (propName.equals(CfgPreferences.PROP_BORDER_COLOR)) { + for (CfgNode n : scene.getNodes()) { + NodeWidget nw = (NodeWidget) scene.findWidget(n); + nw.setBorderColor(prefs.getBorderColor()); + } + } else if (propName.equals(CfgPreferences.PROP_TEXT_FONT)) { + for (CfgNode n : scene.getNodes()) { + NodeWidget nw = (NodeWidget) scene.findWidget(n); + nw.adjustFont(prefs.getTextFont()); + } + } else if (propName.equals(CfgPreferences.PROP_TEXT_COLOR)) { + for (CfgNode n : scene.getNodes()) { + NodeWidget nw = (NodeWidget) scene.findWidget(n); + nw.setForeground(prefs.getTextColor()); + } + } else if (propName.equals(CfgPreferences.PROP_FLAGS)) { + FlagsSetting fs = CfgPreferences.getInstance().getFlagsSetting(); + for (CfgNode n : scene.getNodes()) { + NodeWidget nw = (NodeWidget) scene.findWidget(n); + Color nodeColor = fs.getColor(n.getBasicBlock().getFlags()); + if (nodeColor != null) { + nw.setNodeColor(nodeColor, true); + } else { + nw.setNodeColor(CfgPreferences.getInstance().getNodeColor(), false); + } + } + } else if (propName.equals(CfgPreferences.PROP_SELECTION_COLOR_BG) || propName.equals(CfgPreferences.PROP_SELECTION_COLOR_FG)) { + for (CfgNode n : scene.getNodes()) { + Widget w = scene.findWidget(n); + w.revalidate(); + } + } + scene.validate(); + } + + } + + private Toolbar createToolbar() { + Toolbar tb = new Toolbar("CfgToolbar"); + + tb.setBorder((Border) UIManager.get("Nb.Editor.Toolbar.border")); + + //zoomin/zoomout buttons + tb.add(SystemAction.get(ZoominAction.class).getToolbarPresenter()); + tb.add(SystemAction.get(ZoomoutAction.class).getToolbarPresenter()); + tb.addSeparator(); + + //router buttons + ButtonGroup routerButtons = new ButtonGroup(); + UseDirectLineRouterAction direct = SystemAction.get(UseDirectLineRouterAction.class); + UseBezierRouterAction bezier = SystemAction.get(UseBezierRouterAction.class); + JToggleButton button = (JToggleButton) direct.getToolbarPresenter(); + button.getModel().setGroup(routerButtons); + button.setSelected(true); + tb.add(button); + button = (JToggleButton) bezier.getToolbarPresenter(); + button.getModel().setGroup(routerButtons); + tb.add(button); + tb.addSeparator(); + + //layout buttons + tb.add(SystemAction.get(HierarchicalNodeLayoutAction.class).getToolbarPresenter()); + tb.add(SystemAction.get(HierarchicalCompoundLayoutAction.class).getToolbarPresenter()); + + tb.addSeparator(); + tb.add(SystemAction.get(ShowAllAction.class).getToolbarPresenter()); + tb.addSeparator(); + + //cluster button + tb.add(SystemAction.get(SwitchLoopClustersAction.class).getToolbarPresenter()); + tb.addSeparator(); + + //show/hide edge button + tb.add(SystemAction.get(ShowEdgesAction.class).getToolbarPresenter()); + tb.add(SystemAction.get(HideEdgesAction.class).getToolbarPresenter()); + tb.addSeparator(); + + //color button + JComponent colorButton = SystemAction.get(ColorAction.class).getToolbarPresenter(); + getCfgScene().addCfgEventListener((CfgEventListener) colorButton); + tb.add(colorButton); + + //export button + tb.add(SystemAction.get(ExportAction.class).getToolbarPresenter()); + tb.doLayout(); + + return tb; + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/CfgEventListener.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/CfgEventListener.java new file mode 100644 index 000000000000..0071fa38eb8e --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/CfgEventListener.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.graph; + +import java.util.EventListener; + + +public interface CfgEventListener extends EventListener { + + /** + * the node or the edge selection got changed + */ + public void selectionChanged(CfgScene scene); + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/CfgScene.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/CfgScene.java new file mode 100644 index 000000000000..a5a4233011ef --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/CfgScene.java @@ -0,0 +1,856 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.graph; + + +import at.ssw.visualizer.cfg.model.CfgEdge; +import at.ssw.visualizer.cfg.CfgEditorContext; +import at.ssw.visualizer.cfg.action.ColorAction; +import at.ssw.visualizer.cfg.action.HideEdgesAction; +import at.ssw.visualizer.cfg.action.ShowEdgesAction; +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import at.ssw.visualizer.cfg.graph.layout.HierarchicalCompoundLayout; +import at.ssw.visualizer.cfg.graph.layout.HierarchicalNodeLayout; +import at.ssw.visualizer.cfg.model.CfgEnv; +import at.ssw.visualizer.cfg.model.CfgNode; +import at.ssw.visualizer.cfg.model.LoopInfo; +import at.ssw.visualizer.cfg.preferences.CfgPreferences; +import at.ssw.visualizer.cfg.visual.PolylineRouter; +import at.ssw.visualizer.cfg.visual.PolylineRouterV2; +import at.ssw.visualizer.cfg.visual.WidgetCollisionCollector; +import at.ssw.visualizer.core.selection.Selection; +import at.ssw.visualizer.core.selection.SelectionManager; +import at.ssw.visualizer.model.cfg.BasicBlock; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.swing.AbstractAction; +import javax.swing.JComponent; +import javax.swing.JMenu; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.EventListenerList; +import org.netbeans.api.visual.action.ActionFactory; +import org.netbeans.api.visual.action.MoveProvider; +import org.netbeans.api.visual.action.PopupMenuProvider; +import org.netbeans.api.visual.action.RectangularSelectDecorator; +import org.netbeans.api.visual.action.RectangularSelectProvider; +import org.netbeans.api.visual.action.WidgetAction; +import org.netbeans.api.visual.anchor.Anchor; +import org.netbeans.api.visual.anchor.AnchorFactory; +import org.netbeans.api.visual.graph.GraphScene; +import org.netbeans.api.visual.graph.layout.GraphLayout; +import org.netbeans.api.visual.layout.LayoutFactory; +import org.netbeans.api.visual.layout.SceneLayout; +import org.netbeans.api.visual.router.Router; +import org.netbeans.api.visual.router.Router.*; +import org.netbeans.api.visual.router.RouterFactory; +import org.netbeans.api.visual.widget.ConnectionWidget; +import org.netbeans.api.visual.widget.LayerWidget; +import org.netbeans.api.visual.widget.Widget; +import org.openide.util.actions.SystemAction; + + +public class CfgScene extends GraphScene implements ChangeListener { + private LayerWidget mainLayer = new LayerWidget(this); + private LayerWidget connectionLayer = new LayerWidget(this); + private LayerWidget interractionLayer = new LayerWidget(this); + private LayerWidget clusterLayer = new LayerWidget(this); + private Set selectedNodes = Collections.emptySet(); + private Map loopidx2clusterwidget = new HashMap(); + private Map inputSwitches = new HashMap(); + private Map outputSwitches = new HashMap(); + private WidgetAction moveAction = ActionFactory.createMoveAction (ActionFactory.createFreeMoveStrategy(), this.createMoveProvider()); + private SceneLayout sceneLayout; + private CfgEnv env; + private int currentLayout=-1; + private int currentRouter=-1; + private CfgEditorTopComponent cfgtc; + private EventListenerList listenerList = new EventListenerList(); + private WidgetAction contextPopupAction = this.createContextMenuAction(this); + private List nodeWidgets=null; + private boolean loopClustersVisible = true; + + + public CfgScene(final CfgEditorTopComponent cfgtc){ + addChild(clusterLayer); + addChild(mainLayer); + addChild(interractionLayer); + addChild(connectionLayer); + this.loadDefaults(); + this.cfgtc=cfgtc; + this.loadModel(new CfgEnv(cfgtc.getCfg())); + this.setSceneLayout(CfgEditorContext.LAYOUT_HIERARCHICALNODELAYOUT);//default + this.getInputBindings().setZoomActionModifiers(0); + this.getActions().addAction(ActionFactory.createMouseCenteredZoomAction(1.1)); + this.getActions().addAction(ActionFactory.createPanAction()); + this.getActions().addAction(ActionFactory.createRectangularSelectAction( + this.createSelectDecorator(this), + interractionLayer, + this.createRectangularSelectProvider()) + ); + this.getActions().addAction(this.contextPopupAction); + this.addSceneListener(createSceneListener(this)); + this.validate(); + } + + private void loadModel(CfgEnv cfgenv) { + this.env = cfgenv; + for(CfgNode n : env.getNodes()) { + addNode(n); + } + for(CfgEdge e : env.getEdges()) { + addEdge(e); + setEdgeSource(e, e.getSourceNode()); + setEdgeTarget(e, e.getTargetNode()); + } + this.stackLoops(cfgenv.getLoopMap()); + this.autoHideEdges(); + } + + public void loadDefaults() { + this.setRouter(CfgEditorContext.ROUTING_DIRECTLINES); + CfgPreferences prefs = CfgPreferences.getInstance(); + this.setBackground(prefs.getBackgroundColor()); + } + + //sets the parent Widget of all LoopClusterWidgets + private void stackLoops(Map map) { + this.clusterLayer.removeChildren(); + + Set cache = new HashSet(); + for(LoopInfo info : map.values()){ + if(cache.contains(info)) continue; + LoopClusterWidget widget = this.loopidx2clusterwidget.get(info.getLoopIndex()); + LoopInfo parent = info.getParent(); + while(parent != null){ + LoopClusterWidget parentWidget = this.loopidx2clusterwidget.get(parent.getLoopIndex()); + assert parentWidget != null; + if(widget.getParentWidget()!=null) + widget.removeFromParent(); + parentWidget.addChild(widget); + widget=parentWidget; + parent = parent.getParent(); + } + widget.removeFromParent(); + this.clusterLayer.addChild(widget);//parent == null => parent is clusterlayer + } + } + + //hide in|output edges + private void autoHideEdges(){ + for(CfgNode n : this.getNodes()){ + int fanin = n.getInputEdges().length; + int fanout = n.getOutputEdges().length; + if (fanin > CfgEditorContext.MAX_AUTOEDGESVISIBLE){ + assert(inputSwitches.containsKey(n)); + if(this.inputSwitches.containsKey(n)){ + EdgeSwitchWidget esw = this.inputSwitches.get(n); + esw.changeEdgeVisibility(false); + } + } + if(fanout > CfgEditorContext.MAX_AUTOEDGESVISIBLE){ + if(this.outputSwitches.containsKey(n)){ + EdgeSwitchWidget esw = this.outputSwitches.get(n); + esw.changeEdgeVisibility(false); + } + } + } + + } + + //apply current cfggraphscene layout + public void applyLayout(){ + this.sceneLayout.invokeLayoutImmediately(); + } + + //returns a Set with the currently selected Nodes + public Set getSelectedNodes() { + return Collections.unmodifiableSet(selectedNodes); + } + + + public Map getLoopidx2clusterwidget() { + return loopidx2clusterwidget; + } + + /** + * Sets the color of the currently selected Nodes + * If the supplied color is null the default color will be used + */ + public void setSelectedNodesColor(Color color) { + if(color == null) { //set default color + CfgPreferences prefs = CfgPreferences.getInstance(); + boolean customized=false; + for(CfgNode n : this.selectedNodes){ + color=null; + color = prefs.getFlagsSetting().getColor(n.getBasicBlock().getFlags()); + customized = (color!=null); + NodeWidget nw = (NodeWidget) this.findWidget(n); + nw.setNodeColor((customized) ? color : prefs.getNodeColor(), customized); + } + } else { + for(CfgNode n : this.selectedNodes){ + NodeWidget nw = (NodeWidget) this.findWidget(n); + nw.setNodeColor(color, true); + } + } + this.validate(); + } + + public void setSelectedEdgesVisibility(boolean visible) { + for(CfgNode n : this.selectedNodes){ + EdgeSwitchWidget in = this.inputSwitches.get(n); + EdgeSwitchWidget out = this.outputSwitches.get(n); + if(in != null) in.changeEdgeVisibility(visible); + if(out != null) out.changeEdgeVisibility(visible); + } + this.fireSelectionChanged(); + this.validate(); + } + + public EdgeSwitchWidget getInputSwitch(CfgNode n){ + return this.inputSwitches.get(n); + } + public EdgeSwitchWidget getOutputSwitch(CfgNode n){ + return this.outputSwitches.get(n); + } + + public CfgEnv getCfgEnv() { + return env; + } + + public boolean isLoopClusterVisible() { + return loopClustersVisible; + } + + public void setLoopWidgets(boolean visible) { + for(Widget w : this.loopidx2clusterwidget.values()){ + w.setVisible(visible); + w.revalidate(); + } + this.loopClustersVisible=visible; + this.validate(); + } + + public void setRouter(int newRouter){ + if(newRouter == this.currentRouter) return; + + this.currentRouter=newRouter; + + Router router; + + switch (newRouter) { + case CfgEditorContext.ROUTING_BEZIER: + router = new PolylineRouterV2(new WidgetCollisionCollector() { + public void collectCollisions(List collisions) { + collisions.addAll(getNodeWidgets()); + } + }); + break; + case CfgEditorContext.ROUTING_DIRECTLINES: + router = RouterFactory.createDirectRouter(); + break; + default: + throw new IllegalStateException ("Unknown Router ID: " + newRouter); // NOI18N + } + + for(CfgEdge e : this.getEdges()){ + EdgeWidget ew = (EdgeWidget) this.findWidget(e); + ew.setRouter(router); + } + this.validate(); + } + + + + public Collection getNodeWidgets() { + if(nodeWidgets != null && nodeWidgets.size()==this.getNodes().size()) return nodeWidgets; + + List widgets = new ArrayList(); + for(CfgNode n : this.getNodes()){ + NodeWidget w = (NodeWidget) this.findWidget(n); + widgets.add(w); + } + + nodeWidgets = Collections.unmodifiableList(widgets); + return widgets; + } + + + + public void setSceneLayout(int newLayout){ + + if(currentLayout == newLayout) return; + + GraphLayout graphLayout=null; + + switch (newLayout) { + case CfgEditorContext.LAYOUT_HIERARCHICALNODELAYOUT: + graphLayout = new HierarchicalNodeLayout(this); + break; + + case CfgEditorContext.LAYOUT_HIERARCHICALCOMPOUNDLAYOUT: + graphLayout = new HierarchicalCompoundLayout(this); + break; + } + + this.currentLayout=newLayout; + if(graphLayout != null) + this.sceneLayout=LayoutFactory.createSceneGraphLayout(this, graphLayout); + } + + + @Override + protected void attachEdgeSourceAnchor(CfgEdge edge, CfgNode oldSourceNode, CfgNode sourceNode) { + Anchor sourceAnchor; + EdgeWidget edgeWidget = (EdgeWidget) findWidget (edge); + Widget sourceWidget = findWidget(sourceNode); + + if (edge.isSymmetric()) { + sourceAnchor = new SymmetricAnchor(sourceWidget, true, true); + } else { + sourceAnchor = AnchorFactory.createRectangularAnchor(sourceWidget); + } + edgeWidget.setSourceAnchor (sourceAnchor); + } + + @Override + protected void attachEdgeTargetAnchor(CfgEdge edge, CfgNode oldtarget, CfgNode targetNode) { + Anchor targetAnchor; + ConnectionWidget edgeWidget = (ConnectionWidget) findWidget (edge); + Widget targetWidget = findWidget(targetNode); + + if (edge.isSymmetric()) { + targetAnchor = new SymmetricAnchor(targetWidget, true, false); + } else { + targetAnchor = AnchorFactory.createRectangularAnchor(targetWidget); + } + edgeWidget.setTargetAnchor (targetAnchor); + } + + + @Override + protected Widget attachEdgeWidget(CfgEdge edge) { + EdgeWidget widget = new EdgeWidget(this, edge); + connectionLayer.addChild(widget); + attachSourceSwitchWidget(edge); + attachTargetSwitchWidget(edge); + return widget; + } + + + + @Override + protected Widget attachNodeWidget(CfgNode node) { + this.nodeWidgets = null; + + NodeWidget nw = new NodeWidget(this,node); + WidgetAction.Chain actions = nw.getActions(); + actions.addAction(this.contextPopupAction); + actions.addAction(this.moveAction); + actions.addAction(this.createObjectHoverAction()); + + if ( node.isLoopMember() ) { + LoopClusterWidget loopWidget = this.attachLoopMember(node); + loopWidget.addMember(nw); + } + mainLayer.addChild(nw); + return nw; + } + + + private LoopClusterWidget attachLoopMember(CfgNode node) { + LoopClusterWidget lw = this.loopidx2clusterwidget.get(node.getLoopIndex()); + if(lw == null) { + lw = new LoopClusterWidget(this, node.getLoopDepth(), node.getLoopIndex()); + this.loopidx2clusterwidget.put(node.getLoopIndex(), lw); + this.clusterLayer.addChild(lw); + } + return lw; + } + + + private boolean detachLoopMember(CfgNode node, NodeWidget nodeWidget) { + LoopClusterWidget rm = this.loopidx2clusterwidget.get(node.getLoopIndex()); + if( rm == null) return false;//not added + + if ( rm.removeMember(nodeWidget) ) { + if(rm.getMembers().size() == 0){ + this.loopidx2clusterwidget.remove(rm.getLoopIndex()); + List childs = new ArrayList(rm.getChildren()); + for (Widget w : childs){//append stacked loopwidgets + w.removeFromParent(); + rm.getParentWidget().addChild(w); + } + rm.removeFromParent(); + } + return true; + } + return false; + } + + //this function is not invoked by any class of the module + //however to ensure that the edge switches are treatet corretly + //when a future version removes nodes it was implemented too. + + @Override + protected void detachNodeWidget(CfgNode node, Widget nodeWidget) { + if(node.isLoopMember() && nodeWidget instanceof NodeWidget ) { + this.detachLoopMember(node,(NodeWidget)nodeWidget); + } + super.detachNodeWidget(node, nodeWidget); + assert nodeWidget.getParentWidget()== null; + if(this.inputSwitches.containsKey(node)) { + EdgeSwitchWidget esw = this.inputSwitches.remove(node); + this.connectionLayer.removeChild(esw); + } + if(this.outputSwitches.containsKey(node)){ + EdgeSwitchWidget esw = this.outputSwitches.remove(node); + this.connectionLayer.removeChild(esw); + } + } + + protected EdgeSwitchWidget attachSourceSwitchWidget(CfgEdge e){ + CfgNode sourceNode = e.getSourceNode(); + NodeWidget sourceWidget = (NodeWidget) this.findWidget(sourceNode); + EdgeSwitchWidget out = outputSwitches.get(sourceNode); + if (out==null) { + out = new EdgeSwitchWidget(this, sourceWidget, true); + this.connectionLayer.addChild(out); + outputSwitches.put(sourceNode, out); + } + return out; + } + + + protected EdgeSwitchWidget attachTargetSwitchWidget(CfgEdge e){ + CfgNode targetNode = e.getTargetNode(); + NodeWidget targetWidget = (NodeWidget) this.findWidget(targetNode); + EdgeSwitchWidget in = inputSwitches.get(targetNode); + if (in==null) { + in = new EdgeSwitchWidget(this, targetWidget, false); + this.connectionLayer.addChild(in); + inputSwitches.put(targetNode, in); + } + return in; + } + + //resets the selection state of all NodeWidgets + private void cleanNodeSelection(){ + if( this.selectedNodes.size() != 0) { + this.userSelectionSuggested(Collections.emptySet(), false); + this.selectedNodes = Collections.emptySet(); + this.fireSelectionChanged(); + this.validate(); + } + } + + + //sets the scene & global node selection + public void setNodeSelection(Set newSelection){ + this.setSceneSelection(newSelection); + this.updateGlobalSelection(); + } + + //sets the scene selection + private void setSceneSelection(Set newSelection){ + if(newSelection.equals(selectedNodes)) return; + + this.selectedNodes=newSelection; + + Set selectedObjects = new HashSet(); + + for(CfgNode n : newSelection){ + selectedObjects.addAll(this.findNodeEdges(n, true, true)); + } + selectedObjects.addAll(newSelection); + + //if the selection gets updated from a change in the block view + //the scene will be centered + if(selectionUpdating) + this.centerSelection(); + + this.userSelectionSuggested(selectedObjects, false); + this.fireSelectionChanged(); + this.validate(); + } + + //updates selection of Block View + public void updateGlobalSelection() { + Selection selection = SelectionManager.getDefault().getCurSelection(); + ArrayList newBlocks = new ArrayList(); + for (CfgNode n : this.selectedNodes) { + newBlocks.add(n.getBasicBlock()); + } + BasicBlock[] curBlocks = newBlocks.toArray(new BasicBlock[newBlocks.size()]); + selection.put(curBlocks); + } + + private boolean selectionUpdating = false; + + //change of blockview selection + public void stateChanged(ChangeEvent event) { + if (selectionUpdating) { + return; + } + + selectionUpdating = true; + + Object source = event.getSource(); + if(source instanceof Selection){ + Selection selection=(Selection) source; + Set newSelection = new HashSet(); + BasicBlock[] newBlocks = selection.get(BasicBlock[].class); + if (newBlocks != null) { + for(BasicBlock b : newBlocks){ + for(CfgNode n : this.getNodes()){ + if(n.getBasicBlock() == b) newSelection.add(n); + } + } + this.setSceneSelection(newSelection); + } + } + selectionUpdating = false; + } + + //centers the viewport on the currently selected nodewidgets + private void centerSelection(){ + Point sceneCenter = null; + Collection nodes = this.selectedNodes; + if(nodes.size()==0) { + nodes = this.getNodes(); + } + + for(CfgNode n : nodes) { + if(sceneCenter==null) { + sceneCenter = this.findWidget(n).getLocation(); + continue; + } + Point location = this.findWidget(n).getLocation(); + sceneCenter.x = (location.x+sceneCenter.x)/2; + sceneCenter.y = (location.y+sceneCenter.y)/2; + } + + JComponent view = this.getView (); + if (view != null) { + Rectangle viewBounds = view.getVisibleRect (); + + Point viewCenter = this.convertSceneToView (sceneCenter); + + view.scrollRectToVisible (new Rectangle ( + viewCenter.x - viewBounds.width / 2, + viewCenter.y - viewBounds.height / 2, + viewBounds.width, + viewBounds.height + )); + } + } + + //animated scene Zoom to the max bounds of current viewport + public void zoomScene(){ + JScrollPane pane = this.cfgtc.getJScrollPanel(); + + Rectangle prefBounds = this.getPreferredBounds(); + Dimension viewDim = pane.getViewportBorderBounds().getSize(); + + double realwidth = (double)prefBounds.width*this.getZoomFactor(); + double realheight = (double)prefBounds.height*this.getZoomFactor(); + + double zoomX = (double)viewDim.width / realwidth; + double zoomY = (double)viewDim.height / realheight; + double zoomFactor = Math.min(zoomX, zoomY); + + this.animateZoom(zoomFactor*0.9); + } + + + //animated animateZoom function for scene animateZoom factor + public void animateZoom(double zoomfactor) { + this.getSceneAnimator().animateZoomFactor(this.getZoomFactor() * zoomfactor); + } + + public void addCfgEventListener(CfgEventListener l) { + listenerList.add(CfgEventListener.class, l); + } + + public void removeCfgEventListener(CfgEventListener l) { + listenerList.remove(CfgEventListener.class, l); + } + + public void fireSelectionChanged() { + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==CfgEventListener.class) { + ((CfgEventListener)listeners[i+1]).selectionChanged(this); + } + } + } + + + //Enables Antialiasing + @Override + public void paintChildren () { + Object anti = getGraphics ().getRenderingHint (RenderingHints.KEY_ANTIALIASING); + Object textAnti = getGraphics ().getRenderingHint (RenderingHints.KEY_TEXT_ANTIALIASING); + + getGraphics ().setRenderingHint ( + RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + getGraphics ().setRenderingHint ( + RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + + super.paintChildren (); + + getGraphics ().setRenderingHint (RenderingHints.KEY_ANTIALIASING, anti); + getGraphics ().setRenderingHint (RenderingHints.KEY_TEXT_ANTIALIASING, textAnti); + } + + + //select provider for node selection + private RectangularSelectProvider createRectangularSelectProvider() { + return new RectangularSelectProvider() { + public void performSelection(Rectangle rectangle) { + HashSet set=new HashSet(); + + //change to a rectangle with (x,offsetY) at the top left + if(rectangle.width < 0){ + rectangle.x=rectangle.x+rectangle.width; + rectangle.width*=-1; + } + if(rectangle.height < 0){ + rectangle.y=rectangle.y+rectangle.height; + rectangle.height*=-1; + } + + for(NodeWidget n: getNodeWidgets()) { + Point p = n.getLocation(); + if (p != null && rectangle.contains(p)) { + set.add(n.getNodeModel()); + } + } + setNodeSelection(set); + } + }; + } + + //select decorator for node selection + private RectangularSelectDecorator createSelectDecorator(final CfgScene scene){ + return new RectangularSelectDecorator() { + public Widget createSelectionWidget() { + scene.cleanNodeSelection();//unselected all nodes + scene.revalidate(); + return new SelectionWidget(getScene()); + } + }; + } + + + private MoveProvider createMoveProvider() { + return new MoveProvider(){ + private HashMap originals = new HashMap(); + + private Point original=null; + + public void movementStarted(Widget widget) { + originals.clear(); + NodeWidget nw = (NodeWidget) widget; + if(selectedNodes.contains(nw.getNodeModel())) {//move current selection + for(CfgNode n : selectedNodes){ + Widget w = findWidget(n); + originals.put(w, w.getLocation()); + } + } else {//a newly-selected node will be moved + CfgNode n = nw.getNodeModel(); + HashSet selectedNode = new HashSet(1); + selectedNode.add(n); + setNodeSelection(selectedNode); + originals.put(widget, widget.getPreferredLocation()); + widget.revalidate(); + validate(); + + } + } + + public void movementFinished(Widget widget) { + NodeWidget nw = (NodeWidget) widget; + if(selectedNodes.contains(nw.getNodeModel())) { + return;//to be able to move the current selection + } + + HashSet selectedNode = new HashSet(1); + selectedNode.add(nw.getNodeModel()); + setNodeSelection(selectedNode); + originals.clear (); + original = null; + + } + + public Point getOriginalLocation(Widget widget) { + if(original==null) + original = widget.getLocation(); + + return original; + } + //todo : find a cache algorithm which only routes edges + //which are intersected by bounds of the moved rectangle + public void setNewLocation(Widget widget, Point location) { + Point org = getOriginalLocation(widget); + int dx = location.x - org.x; + int dy = location.y - org.y; + for (Map.Entry entry : originals.entrySet ()) { + Point point = entry.getValue (); + entry.getKey ().setPreferredLocation (new Point (point.x + dx, point.y + dy)); + } + for(CfgEdge e : getEdges()) { + EdgeWidget ew = (EdgeWidget) findWidget(e); + if(ew.isVisible()) + ew.reroute(); + } + } + }; + } + + + private WidgetAction createContextMenuAction(final CfgScene scene) { + return ActionFactory.createPopupMenuAction(new PopupMenuProvider() { + public JPopupMenu getPopupMenu(Widget widget, Point point) { + JPopupMenu menu = new JPopupMenu(); + NodeWidget nw = null; + if(widget instanceof NodeWidget) { + nw = (NodeWidget) widget; + if(!selectedNodes.contains(nw.getNodeModel())){ + HashSet selectedNode = new HashSet(1); + selectedNode.add(nw.getNodeModel()); + setNodeSelection(selectedNode); + } + } else if (scene.getSelectedNodes().size() == 1) { + nw = (NodeWidget) scene.findWidget(scene.getSelectedNodes().iterator().next()); + } + + if(nw != null){ + CfgNode node = nw.getNodeModel(); + ArrayList successors = new ArrayList(); + ArrayList predecessors = new ArrayList(); + for(CfgEdge e : node.getOutputEdges()){ + successors.add(e.getTargetNode()); + } + for(CfgEdge e : node.getInputEdges()){ + predecessors.add(e.getSourceNode()); + } + + if(predecessors.size()>0){ + Collections.sort(predecessors, new NodeNameComparator()); + JMenu predmenu = new JMenu("Go to predecessor"); + for (CfgNode n : predecessors) { + GotoNodeAction action = new GotoNodeAction(n); + predmenu.add(action); + } + menu.add(predmenu); + } + if(successors.size()>0){ + Collections.sort(successors, new NodeNameComparator()); + JMenu succmenu = new JMenu("Go to successor"); + for (CfgNode n : successors) { + GotoNodeAction action = new GotoNodeAction(n); + succmenu.add(action); + } + menu.add(succmenu); + } + if ( successors.size() > 0 || predecessors.size() > 0) + menu.addSeparator(); + } + + menu.add(SystemAction.get(ShowEdgesAction.class)); + menu.add(SystemAction.get(HideEdgesAction.class)); + menu.addSeparator(); + menu.add(SystemAction.get(ColorAction.class).getPopupPresenter()); + return menu; + } + }); + } + + private class NodeNameComparator implements Comparator { + public int compare(CfgNode node1, CfgNode node2) { + String name1 = node1.getBasicBlock().getName().substring(1); + String name2 = node2.getBasicBlock().getName().substring(1); + Integer blocknum1 = Integer.parseInt(name1); + Integer blocknum2 = Integer.parseInt(name2); + return blocknum1.compareTo(blocknum2); + } + } + + private class GotoNodeAction extends AbstractAction { + CfgNode node ; + + GotoNodeAction(CfgNode node){ + super(node.getBasicBlock().getName()); + this.node = node; + } + public void actionPerformed(ActionEvent e) { + Set nodes = new HashSet(1); + nodes.add(node); + setNodeSelection(nodes); + centerSelection(); + } + } + + private SceneListener createSceneListener(final CfgScene scene){ + return new SceneListener() { + + public void sceneRepaint() { + } + + public void sceneValidating() { + } + + public void sceneValidated() { + if (scene.isLoopClusterVisible()){ //update only if visible + for(LoopClusterWidget cw : getLoopidx2clusterwidget().values()){ + cw.updateClusterBounds(); + } + } + for(EdgeSwitchWidget esw : inputSwitches.values()){ + esw.updatePosition(); + } + + for(EdgeSwitchWidget esw : outputSwitches.values()){ + esw.updatePosition(); + } + } + }; + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/EdgeSwitchWidget.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/EdgeSwitchWidget.java new file mode 100644 index 000000000000..eeab28034966 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/EdgeSwitchWidget.java @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.graph; + +import at.ssw.visualizer.cfg.model.CfgEdge; +import at.ssw.visualizer.cfg.model.CfgNode; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.util.ArrayList; +import java.util.Collection; +import org.netbeans.api.visual.action.ActionFactory; +import org.netbeans.api.visual.action.SelectProvider; +import org.netbeans.api.visual.action.TwoStateHoverProvider; +import org.netbeans.api.visual.action.WidgetAction; +import org.netbeans.api.visual.model.ObjectState; +import org.netbeans.api.visual.widget.Widget; + + +public class EdgeSwitchWidget extends Widget { + private final static Color color_enabled = Color.gray; + private final static Color color_hover = Color.lightGray; + private float width=1; + private float height=1; + private CfgScene scene; + private NodeWidget nodeWidget; + private boolean output; + private WidgetAction hoverAction; + private static final String TT_HIDE_EDGES = "Hide Edges"; + private static final String TT_SHOW_EDGES = "Show Edges"; + private static SelectProvider selectProvider = createSelectProvider(); + + + public EdgeSwitchWidget(final CfgScene scene, NodeWidget nodeWidget, boolean output) { + super(scene); + this.scene = scene; + this.output = output; + this.nodeWidget = nodeWidget; + + this.getActions().addAction(ActionFactory.createSelectAction(selectProvider)); + TwoStateHoverProvider ts = new TsHover(this); + WidgetAction wa = ActionFactory.createHoverAction(ts); + this.hoverAction = wa; + this.getActions().addAction(wa); + scene.getActions().addAction(wa); + this.setToolTipText(TT_HIDE_EDGES); + this.setForeground(color_enabled); + this.setState(ObjectState.createNormal()); + } + + + @Override + protected Rectangle calculateClientArea() { + if (this.nodeWidget.getBounds() == null) return new Rectangle(0, 0, 1, 1); + int hw = (int) (this.width / 2); + int hh = (int) (this.height /2); + + return new Rectangle(-hw, -hh, 2*hw, 2*hh); + } + + + public void updatePosition() { + if (this.nodeWidget.getBounds() != null) { + this.width = nodeWidget.getBounds().width*9; + this.width /=10; + this.height = nodeWidget.getBounds().height/4; + int offset=(int)(2 * (height / 3)); + + Rectangle bounds = nodeWidget.getBounds(); + Point location = nodeWidget.getLocation(); + + Point newLoc = new Point(); + newLoc.x = location.x; + + if(output) { + newLoc.y = +location.y + bounds.height/2+offset; + }else { + newLoc.y = location.y - bounds.height/2-offset; + } + this.setPreferredLocation(newLoc); + } + } + + private Collection getEdges(){ + Collection edges; + CfgNode node = nodeWidget.getNodeModel(); + if (output) { + edges = scene.findNodeEdges(node, true, false); + } else { + edges = scene.findNodeEdges(node, false, true); + } + return edges; + } + + //change visibility for all Edges + public void changeEdgeVisibility(boolean visible){ + Collection edges = this.getEdges(); + + for(CfgEdge e: edges) { + EdgeWidget ew = (EdgeWidget) scene.findWidget(e); + if(visible != ew.isEdgeVisible()){ + ew.setEdgeVisible(visible); + if(output){ + scene.getInputSwitch(e.getTargetNode()).updateStatus(); + } else { + scene.getOutputSwitch(e.getSourceNode()).updateStatus(); + } + } + } + if(visible) + this.setToolTipText(TT_HIDE_EDGES); + else + this.setToolTipText(TT_SHOW_EDGES); + + this.setForeground(color_enabled); + this.bringToBack(); + ObjectState os = this.getState(); + this.setState(os.deriveSelected(!visible)); + } + + /** + * Update the status of the switch to the current state of the edges + * usually needed when the opposit switch changes the state + */ + private void updateStatus(){ + Collection edges = this.getEdges(); + boolean hiddenFound=false; + for(CfgEdge e: edges) { + EdgeWidget ew = (EdgeWidget) scene.findWidget(e); + if(!ew.isVisible()) { + hiddenFound=true; + break; + } + } + ObjectState os = this.getState(); + if(os.isSelected() && !hiddenFound) { + this.setState(os.deriveSelected(false)); + setToolTipText(TT_HIDE_EDGES); + } else if (!os.isSelected() && hiddenFound) { + this.setState(os.deriveSelected(true)); + setToolTipText(TT_SHOW_EDGES); + } + this.revalidate(); + } + + + public void startPreview() { + ObjectState os = this.getState(); + + for(CfgEdge e : getEdges()) { + EdgeWidget ew = (EdgeWidget) scene.findWidget(e); + if(!os.isSelected() || !ew.isVisible()){ + ObjectState edgeState = ew.getState(); + ew.setState(edgeState.deriveHighlighted(true)); + } + } + } + + public void endPreview(){ + for(CfgEdge e : getEdges()) { + EdgeWidget ew = (EdgeWidget) scene.findWidget(e); + ObjectState os = ew.getState(); + ew.setState(os.deriveHighlighted(false)); + } + } + + /** + * shows or hides the edges of the switch + */ + public void switchEdges() { + endPreview(); + ObjectState os = this.getState(); + Collection edges = this.getEdges(); + ArrayList updates = new ArrayList(); + boolean visible=os.isSelected(); + this.setState(os.deriveSelected(!visible)); + for(CfgEdge e: edges) { + EdgeWidget ew = (EdgeWidget) scene.findWidget(e); + if(ew.isEdgeVisible() != visible){ + updates.add(e); + ew.setEdgeVisible(visible); + if(output){ + scene.getInputSwitch(e.getTargetNode()).updateStatus(); + } else { + scene.getOutputSwitch(e.getSourceNode()).updateStatus(); + } + } + } + if(visible) + this.setToolTipText(TT_HIDE_EDGES); + else + this.setToolTipText(TT_SHOW_EDGES); + + scene.fireSelectionChanged();//updates Edge visibility for context action + revalidate(); + } + + + + + private class TsHover implements TwoStateHoverProvider { + EdgeSwitchWidget tw; + + TsHover(EdgeSwitchWidget tw) { + this.tw = tw; + } + + public void unsetHovering(Widget w) { + w.setForeground(color_enabled); + ObjectState state = w.getState(); + w.setState(state.deriveWidgetHovered(false)); + w.bringToBack(); + endPreview(); + } + + public void setHovering(Widget w) { + ObjectState state = w.getState(); + w.setState(state.deriveWidgetHovered(true)); + w.setForeground(color_hover); + w.bringToFront(); + nodeWidget.bringToFront(); + startPreview(); + } + } + + @Override + public void paintWidget() { + ObjectState os = this.getState(); + if(!os.isHovered() && !os.isSelected()) return; //all previewEdges visible and not hovering, + //no need to paint the switch + float hw = width/2; + Polygon pol = new Polygon(); + pol.addPoint(0,(int) -height/2); + pol.addPoint((int)hw,(int) height/2); + pol.addPoint((int)-hw,(int) height/2); + Graphics2D gr = getGraphics(); + gr.setColor(this.getForeground()); + BasicStroke bs = new BasicStroke(2.0f, BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND); + gr.setStroke(bs); + AffineTransform previousTransform; + previousTransform = gr.getTransform (); + if(output) { + if(os.isSelected() ){//hidden + gr.scale(1.0, -1.0); + } + } else { //input switch + if(os.isHovered() && !os.isSelected()){ + gr.scale(1.0, -1.0); + } + } + gr.fillPolygon(pol); + gr.setTransform(previousTransform); + + } + + + + //the constructor adds the hover WidgetAction to the scene + //the action is removed from the scene when the object gets destroyed + @Override + protected void finalize() throws Throwable { + this.getScene().getActions().removeAction(hoverAction); + this.getActions().removeAction(hoverAction); + } + + @Override + public String toString(){ + return "EDGESWITCH("+this.nodeWidget.getNodeModel().toString()+")"; + } + + private static SelectProvider createSelectProvider() { + return new SelectProvider(){ + public boolean isAimingAllowed(Widget arg0, Point arg1, boolean arg2) { + return false; + } + + public boolean isSelectionAllowed(Widget arg0, Point arg1, boolean arg2) { + return true; + } + + public void select(Widget w, Point arg1, boolean arg2) { + if(w instanceof EdgeSwitchWidget){ + EdgeSwitchWidget tw = (EdgeSwitchWidget) w; + tw.switchEdges(); + } + } + }; + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/EdgeWidget.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/EdgeWidget.java new file mode 100644 index 000000000000..1e79de09b170 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/EdgeWidget.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.graph; + +import at.ssw.visualizer.cfg.model.CfgEdge; +import at.ssw.visualizer.cfg.preferences.CfgPreferences; +import at.ssw.visualizer.cfg.visual.BezierWidget; +import at.ssw.visualizer.cfg.visual.SplineConnectionWidget; +import org.netbeans.api.visual.anchor.AnchorShapeFactory; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Stroke; +import org.netbeans.api.visual.anchor.AnchorShape; +import org.netbeans.api.visual.model.ObjectState; + +//public class EdgeWidget extends BezierWidget { +public class EdgeWidget extends SplineConnectionWidget { + + private boolean visible=true;//to store the visible state when entering the preview + protected static final Stroke selectedStroke = new BasicStroke(3.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); + protected static final Stroke defaultStroke = new BasicStroke(1.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); + protected static final Stroke previewStroke = new BasicStroke( + 0.5f, // Width + BasicStroke.CAP_SQUARE, // End cap + BasicStroke.JOIN_MITER, // Join style + 10.0f, // Miter limit + new float[] {5.0f, 5.0f}, // Dash pattern + 0.0f); + + + + public EdgeWidget(CfgScene scene, CfgEdge edge) { + super(scene); + Color lineColor; + CfgPreferences prefs = CfgPreferences.getInstance(); + + if(edge.isBackEdge()) + lineColor = prefs.getBackedgeColor(); + else if (edge.isXhandler()) + lineColor = prefs.getExceptionEdgeColor(); + else + lineColor = prefs.getEdgeColor(); + + setLineColor(lineColor); + AnchorShape as; + if(edge.isReflexive())//small Arrow + as = AnchorShapeFactory.createTriangleAnchorShape(6, true, false, 5); + else + as =AnchorShapeFactory.createTriangleAnchorShape(10, true, false, 9); + + setTargetAnchorShape(as); + setToolTipText(edge.toString()); + } + + public CfgEdge getEdgeModel() { + CfgScene scene = (CfgScene) this.getScene(); + return (CfgEdge) scene.findObject(this); + } + + public void setEdgeVisible(boolean visible) { + this.visible=visible; + this.setVisible(visible); + this.reroute(); + this.revalidate(); + } + + + public boolean isEdgeVisible(){ + return visible; + } + + + @Override + public void notifyStateChanged(ObjectState oldState, ObjectState newState) { + setForeground (getLineColor()); + + if(newState.isHighlighted() && !oldState.isHighlighted()){ + this.setStroke(previewStroke); + this.setVisible(true); + } else { + if(newState.isSelected()){ + this.setStroke(selectedStroke); + } else { + this.setStroke(defaultStroke); + } + if(this.isEdgeVisible()){ + this.setVisible(true); + } else { + this.setVisible(false); + } + } + } + + + @Override + public String toString(){ + return "EdgeWidget[" + getEdgeModel().toString() + "]"; + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/LoopClusterWidget.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/LoopClusterWidget.java new file mode 100644 index 000000000000..3bd8d95634b3 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/LoopClusterWidget.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.graph; + +import at.ssw.visualizer.cfg.model.LoopInfo; +import java.awt.Color; +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import org.netbeans.api.visual.action.ActionFactory; +import org.netbeans.api.visual.action.EditProvider; +import org.netbeans.api.visual.border.BorderFactory; +import org.netbeans.api.visual.widget.Widget; + +public class LoopClusterWidget extends Widget implements Comparable { + private static final int INSET = 10;//min space between node members and cluster border + private static final int DASHSIZE = 10; + private Color color = Color.BLUE; + private int loopIndex; + private int loopDepth; + private CfgScene cfgscene; + private ArrayList members = new ArrayList(); + + public LoopClusterWidget(CfgScene scene, int loopdepth, final int loopindex) { + super(scene); + this.cfgscene = scene; + this.loopIndex = loopindex; + this.loopDepth = loopdepth; + this.setBorder(BorderFactory.createDashedBorder(color, DASHSIZE, DASHSIZE/2, true)); + this.getActions().addAction(ActionFactory.createEditAction( new EditProvider() { //double click action + public void edit(Widget w) { + if(w instanceof LoopClusterWidget){ + for(LoopInfo info : cfgscene.getCfgEnv().getLoopMap().values()){ + if(info.getLoopIndex() == loopindex){ + cfgscene.setNodeSelection(info.getMembers()); + break; + } + } + } + } + })); + + } + + public List getMembers() { + return members; + } + + public int getLoopIndex() { + return loopIndex; + } + + public void addMember(NodeWidget nw) { + assert(!this.members.contains(nw)); + members.add(nw); + } + + public boolean removeMember(NodeWidget nw) { + if(this.members.contains(nw)){ + members.remove(nw); + return true; + } + return false; + } + + public void setrandomColor(){ + if(this.loopDepth == 0 ) return; + Random rand = new Random(); + Color randColor = Color.getHSBColor(rand.nextFloat()%360,0.1f,1.0f); + this.setBackground(randColor); + } + + //updates the bounds of the widget, + //and revalidates the widget if a membernode changed the scene position + public void updateClusterBounds(){ + Rectangle boundRect=null; + + for(NodeWidget nw : this.members){ + if(boundRect==null){ + boundRect = nw.convertLocalToScene(nw.getBounds()); + } else { + boundRect = boundRect.union(nw.convertLocalToScene(nw.getBounds())); + } + } + if(boundRect==null) return; + for(Widget w : this.getChildren()) { + if(w instanceof LoopClusterWidget) { + LoopClusterWidget lc = (LoopClusterWidget)w; + lc.updateClusterBounds(); + boundRect = boundRect.union(w.convertLocalToScene(w.getBounds())); + } + } + + boundRect.grow(INSET, INSET); + this.setPreferredBounds(boundRect); + } + + + public int compareTo(LoopClusterWidget o) { + return new Integer(this.loopDepth).compareTo(o.loopDepth); + } + + + @Override + public String toString(){ + return "LoopCluster: [DEPTH "+this.loopDepth+ "] [INDEX "+this.loopIndex+"]"; + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/NodeWidget.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/NodeWidget.java new file mode 100644 index 000000000000..8080a05348df --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/NodeWidget.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.graph; + +import at.ssw.visualizer.cfg.model.CfgNode; +import at.ssw.visualizer.cfg.preferences.CfgPreferences; +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.font.FontRenderContext; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import org.netbeans.api.visual.border.BorderFactory; +import org.netbeans.api.visual.model.ObjectState; +import org.netbeans.api.visual.widget.Widget; + + +public class NodeWidget extends Widget { + + + //basic block node dimension + private final int halfheight=12; + private final int halfwidth=17; + + private final int height=halfheight*2+1; + private final int width=halfwidth*2+1; + private final int arcHeight=(halfheight/4)*3; + private final int arcWidth = arcHeight; + private final int FONT_MAXSIZE=18; + + private int borderWidth; + private boolean selected=false; + private boolean nodeColorCustomized; + private String text; + private Rectangle2D fontRect; + private Color nodeColor; + + protected static final Color HOVER_BACKGROUND = new Color(0xEEEEEE); + protected static final Color HOVER_FOREGROUND = new Color(0xCDCDCD); + + public NodeWidget(CfgScene scene, CfgNode nodeModel ){ + super(scene); + this.setToolTipText("" + nodeModel.getDescription().replaceAll("\n", "
") + ""); + this.selected=false; + this.text = nodeModel.getBasicBlock().getName(); + this.borderWidth = nodeModel.getLoopDepth()+1; + this.setBorder(BorderFactory.createRoundedBorder(arcWidth+borderWidth, arcHeight+borderWidth, borderWidth, borderWidth, Color.BLACK, Color.BLACK)); + CfgPreferences prefs = CfgPreferences.getInstance(); + Color color = prefs.getFlagsSetting().getColor(nodeModel.getBasicBlock().getFlags()); + this.nodeColorCustomized = (color!=null); + this.nodeColor = (nodeColorCustomized) ? color : prefs.getNodeColor(); + this.adjustFont(null); + } + + public void setBorderColor(Color color){ + this.setBorder(BorderFactory.createRoundedBorder(arcWidth+borderWidth, arcHeight+borderWidth, borderWidth, borderWidth, color, color)); + } + + public boolean isNodeColorCustomized() { + return nodeColorCustomized; + } + + //sets a customColor node color + public void setNodeColor(Color color, boolean customColor) { + this.nodeColorCustomized=customColor; + this.nodeColor=color; + this.revalidate(); + } + + public Color getNodeColor() { + return this.nodeColor; + } + + public CfgNode getNodeModel() { + CfgScene scene = (CfgScene) this.getScene(); + return (CfgNode) scene.findObject(this); + } + + + @Override + public void notifyStateChanged(ObjectState oldState, ObjectState newState) { + if(!oldState.equals(newState)) + this.revalidate(); + if(!oldState.isSelected() && newState.isSelected()) + this.bringToFront(); + } + + @Override + protected Rectangle calculateClientArea() { + return new Rectangle(-(halfwidth+1), -(1+halfheight), width+1, height+1);//add border + } + + public void adjustFont(Font font){ + if(font==null) + font = CfgPreferences.getInstance().getTextFont(); + if(font.getSize()>FONT_MAXSIZE){ + font = new Font(font.getFamily(), font.getStyle(), FONT_MAXSIZE); + } + int size=font.getSize(); + int fontStyle = font.getStyle(); + String fontName = font.getFamily(); + FontRenderContext frc = new FontRenderContext(new AffineTransform(), false, false); + Rectangle2D bounds = font.getStringBounds(text, frc); + while(size > 1 && bounds.getWidth() > width) { + font = new Font(fontName, fontStyle, --size); + bounds = font.getStringBounds(text, frc); + } + this.fontRect=bounds; + this.setFont(font); + } + + @Override + protected void paintWidget() { + Graphics2D gr = getGraphics(); + gr.setColor(nodeColor); + Insets borderInsets = this.getBorder().getInsets(); + RoundRectangle2D.Float innerRect = new RoundRectangle2D.Float(-(halfwidth+1), -(halfheight+1), width+1, height+1,arcWidth-1, arcHeight-1); + gr.fill(innerRect); + gr.setColor(getForeground()); + gr.setFont(getFont()); + float textX = (float)( - fontRect.getCenterX()); + float textY = (float)( - fontRect.getCenterY()); + gr.drawString(text, textX, textY); + + RoundRectangle2D.Float outerRect = new RoundRectangle2D.Float(-(halfwidth+borderInsets.left + 1), -(halfheight+borderInsets.top + 1), + width+borderInsets.left + borderInsets.right + 1, height + borderInsets.top + borderInsets.bottom + 1, + arcWidth + borderWidth, arcHeight + borderWidth); + + ObjectState os =this.getState(); + if(os.isSelected()){ + Composite composite = gr.getComposite(); + gr.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.5f)); + gr.setColor(CfgPreferences.getInstance().getSelectionColorForeground()); + gr.fill(outerRect); + gr.setColor(CfgPreferences.getInstance().getSelectionColorBackground()); + gr.setComposite(composite); + } + if(os.isHovered()){ + Composite composite = gr.getComposite(); + gr.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.5f)); + gr.setColor(HOVER_FOREGROUND); + gr.fill(outerRect); + gr.setColor(HOVER_BACKGROUND); + gr.setComposite(composite); + } + } + + @Override + public String toString() { + return "NodeWidget[" + getNodeModel().getBasicBlock().getName() + "]"; + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/SelectionWidget.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/SelectionWidget.java new file mode 100644 index 000000000000..30de3bd57e9d --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/SelectionWidget.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.graph; + +import at.ssw.visualizer.cfg.preferences.CfgPreferences; +import java.awt.AlphaComposite; +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import org.netbeans.api.visual.widget.Scene; +import org.netbeans.api.visual.widget.Widget; + + +public class SelectionWidget extends Widget { + public SelectionWidget(Scene scene) { + super(scene); + } + + public static void renderSelectedRect(Graphics2D gr, Rectangle rect) { + if (rect == null) { + return; + } + gr.setColor(CfgPreferences.getInstance().getSelectionColorBackground()); + gr.fillRect(rect.x, rect.y, rect.width, rect.height); + gr.setColor(CfgPreferences.getInstance().getSelectionColorForeground()); + } + + public void renderSelectionRectangle(Graphics2D gr, Rectangle selectionRectangle) { + Composite composite = gr.getComposite(); + gr.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.5f)); + + renderSelectedRect(gr, selectionRectangle); + gr.setComposite(composite); + } + + @Override + public void paintWidget(){ + this.renderSelectionRectangle(this.getGraphics(), this.getBounds()); + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/SymmetricAnchor.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/SymmetricAnchor.java new file mode 100644 index 000000000000..3e20272830be --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/SymmetricAnchor.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.graph; + +import org.netbeans.api.visual.widget.Widget; +import org.netbeans.api.visual.anchor.Anchor; +import java.awt.*; + + +/** + * This Anchor can be used with symmetric edges to create parallel Edges. + * Two Directed Edges are symmetric if they are connecting the same Nodes in different directions. + * e.g. Nodes (1, 2) Edges(a , b) with (1)-a->(2) , (2)-b->(1) + * Start-/End positions are calculated with a fixed offset to prevent edges from overlapping. + * If the edges are drawn as straight lines they will appear as parallel edges. + */ + +public final class SymmetricAnchor extends Anchor { + + private final static int OFFSET = 10; + private boolean includeBorders; + private int offx; + private int offy; + + public SymmetricAnchor (Widget widget, boolean includeBorders, boolean source) { + super (widget); + this.includeBorders = includeBorders; + if (source) { + offx = OFFSET; + offy = OFFSET; + } + else { + offx = -OFFSET; + offy = -OFFSET; + } + } + + public Result compute (Entry entry) { + Point relatedLocation = //center of the widget + getRelatedSceneLocation (); + Point oppositeLocation = //center of the widget + getOppositeSceneLocation (entry); + + Widget widget = getRelatedWidget (); + Rectangle bounds = widget.getBounds (); + if (! includeBorders) { + Insets insets = widget.getBorder ().getInsets (); + bounds.x += insets.left; + bounds.y += insets.top; + bounds.width -= insets.left + insets.right; + bounds.height -= insets.top + insets.bottom; + } + + bounds = widget.convertLocalToScene (bounds); + + if (bounds.isEmpty () || relatedLocation.equals (oppositeLocation)) + return new Anchor.Result (relatedLocation, Anchor.DIRECTION_ANY); + + float dx //distance x-axis + = oppositeLocation.x - relatedLocation.x; + float dy //distance y-axis + = oppositeLocation.y - relatedLocation.y; + + + float ddx + = Math.abs (dx) / (float) bounds.width; + float ddy = + Math.abs (dy) / (float) bounds.height; + + Anchor.Direction direction; + + + if (ddx >= ddy) { + if(dx >= 0.0f){ + direction = Direction.RIGHT; + relatedLocation.y -= offy; + } else { + direction = Direction.LEFT; + relatedLocation.y += offy; + } + } else { + if(dy >= 0.0f){ + direction = Direction.BOTTOM; + relatedLocation.x += offx; + } else { + direction = Direction.TOP; + relatedLocation.x -= offx; + } + } + + + float scale = 0.5f / Math.max (ddx, ddy); + + float ex = scale * dx; + float ey = scale * dy; + + Point point = new Point (Math.round (relatedLocation.x + ex), Math.round (relatedLocation.y + ey)); + + if(direction == Direction.RIGHT) { + int top = bounds.y;//-bounds.height;// left y of the widget + int bottom = bounds.y + bounds.height;// right y of the widget + if(point.y < top) {//above the widget + int cor = top-point.y; + point.x -= cor; + point.y += cor; + } else if ( point.y > bottom) { + int cor = point.y-bottom; + point.x -= cor; + point.y -= cor; + } + + } else if (direction == Direction.LEFT) { + int top = bounds.y;//-bounds.height;// left y of the widget + int bottom = bounds.y + bounds.height;// right y of the widget + + + + if(point.y < top) {//above the widget + int cor = top-point.y; + point.x += cor; + point.y += cor; + } else if ( point.y > bottom) { + int cor = bottom-point.y; + point.x -= cor; + point.y += cor; + } + + + } else if (direction == Direction.BOTTOM) { + int left = bounds.x;//-bounds.height;// left y of the widget + int right = bounds.x + bounds.width;// right y of the widget + if(point.x < left) {//above the widget + int cor = left-point.x; + point.x += cor; + point.y -= cor; + } else if ( point.x > right) { + int cor = point.x- right; + point.x -= cor; + point.y -= cor; + } + + } else if (direction == Direction.TOP) { + int left = bounds.x;//-bounds.height;// left y of the widget + int right = bounds.x + bounds.width;// right y of the widget + if(point.x < left) {//above the widget + int cor = left-point.x; + point.x += cor; + point.y += cor; + } else if ( point.x > right) { + int cor = point.x - right; + point.x -= cor; + point.y += cor; + } + + } + + return new Anchor.Result (point, direction); + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/layout/HierarchicalCompoundLayout.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/layout/HierarchicalCompoundLayout.java new file mode 100644 index 000000000000..c726a2605a71 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/layout/HierarchicalCompoundLayout.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.graph.layout; + +import at.ssw.visualizer.cfg.graph.CfgScene; +import at.ssw.visualizer.cfg.model.CfgEdge; +import at.ssw.visualizer.cfg.model.CfgNode; +import at.ssw.visualizer.cfg.model.LoopInfo; +import java.awt.Point; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.eclipse.draw2d.geometry.Insets; +import org.netbeans.api.visual.graph.layout.GraphLayout; +import org.netbeans.api.visual.graph.layout.UniversalGraph; +import org.eclipse.draw2d.graph.CompoundDirectedGraph; +import org.eclipse.draw2d.graph.CompoundDirectedGraphLayout; +import org.eclipse.draw2d.graph.Edge; +import org.eclipse.draw2d.graph.EdgeList; +import org.eclipse.draw2d.graph.Node; +import org.eclipse.draw2d.graph.NodeList; +import org.eclipse.draw2d.graph.Subgraph; +import org.netbeans.api.visual.widget.Widget; + +public class HierarchicalCompoundLayout extends GraphLayout { + + private static final int TOP_BORDER = 20; + private static final int LEFT_BORDER = 20; + private int PADDING = 20; + private static final int INSET = 20; + private CfgScene scene; + + public HierarchicalCompoundLayout(CfgScene scene){ + this.scene = scene; + } + + @Override + protected void performGraphLayout(UniversalGraph ug) { + CompoundDirectedGraph dg = new CompoundDirectedGraph(); + CompoundDirectedGraphLayout layout = new CompoundDirectedGraphLayout(); + NodeList nodeList = dg.nodes; + EdgeList edgeList = dg.edges; + + Map idx2graph = new HashMap(); + Subgraph base = new Subgraph(0); + idx2graph.put(0, base); + base.insets=getInsets(); + for(LoopInfo info : scene.getCfgEnv().getLoopMap().values()){ + Subgraph subg = new Subgraph(info.getLoopIndex()); + subg.insets=getInsets(); + idx2graph.put(info.getLoopIndex(), subg); + } + + for(CfgNode n : scene.getCfgEnv().getNodes() ) { + Widget nodeWidget = scene.findWidget(n); + Node node = new Node(n); + node.width=nodeWidget.getBounds().width; + node.height = nodeWidget.getBounds().height; + node.setPadding(new Insets(PADDING, PADDING, PADDING, PADDING)); + Subgraph subg = idx2graph.get(n.getLoopIndex()); + assert(subg != null); + node.setParent(subg); + subg.addMember(node); + nodeList.add(node); + + } + nodeList.addAll(idx2graph.values()); + for(LoopInfo info : scene.getCfgEnv().getLoopMap().values()){ + Subgraph subg = idx2graph.get(info.getLoopIndex()); + if(info.getParent() != null){ + Subgraph parentsubg = idx2graph.get(info.getParent().getLoopIndex()); + Edge edge = new Edge(parentsubg, subg); + parentsubg.addMember(subg); + subg.setParent(parentsubg); + edgeList.add(edge); + } + } + for(CfgEdge e : scene.getCfgEnv().getEdges() ) { + if(e.isBackEdge()) continue; + Edge edge = new Edge(e, nodeList.getNode(e.getSourceNode().getNodeIndex()), nodeList.getNode(e.getTargetNode().getNodeIndex())); + edgeList.add(edge); + } + layout.visit(dg); + + for(Object obj : dg.nodes){ + Node n = (Node) obj; + if(n.data instanceof CfgNode){ + CfgNode cfgNode = (CfgNode) n.data; + Point pos = new Point(n.x + LEFT_BORDER, n.y + TOP_BORDER); + Point scenepos = scene.convertLocalToScene(pos); + this.setResolvedNodeLocation(ug, cfgNode, scenepos); + } + } + } + + @Override + protected void performNodesLayout(UniversalGraph ug, Collection collection) { + } + + private Insets getInsets(){ + return new Insets(INSET, INSET, INSET, INSET); + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/layout/HierarchicalNodeLayout.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/layout/HierarchicalNodeLayout.java new file mode 100644 index 000000000000..0b77ded992ce --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/graph/layout/HierarchicalNodeLayout.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.graph.layout; + +import at.ssw.visualizer.cfg.graph.CfgScene; +import at.ssw.visualizer.cfg.model.CfgEdge; +import at.ssw.visualizer.cfg.model.CfgNode; +import java.awt.Point; +import java.util.Collection; +import org.netbeans.api.visual.graph.layout.GraphLayout; +import org.netbeans.api.visual.graph.layout.UniversalGraph; +import org.eclipse.draw2d.graph.DirectedGraph; +import org.eclipse.draw2d.graph.DirectedGraphLayout; +import org.eclipse.draw2d.graph.Edge; +import org.eclipse.draw2d.graph.EdgeList; +import org.eclipse.draw2d.graph.Node; +import org.eclipse.draw2d.graph.NodeList; +import org.netbeans.api.visual.widget.Widget; + + +public class HierarchicalNodeLayout extends GraphLayout { + + private static final int TOP_BORDER = 20; + private static final int LEFT_BORDER = 40; + + private CfgScene scene; + + public HierarchicalNodeLayout(CfgScene scene){ + this.scene = scene; + } + + @Override + protected void performGraphLayout(UniversalGraph ug) { + DirectedGraph dg = new DirectedGraph(); + DirectedGraphLayout layout = new DirectedGraphLayout(); + + NodeList nodeList = dg.nodes; + EdgeList edgeList = dg.edges; + + for(CfgNode n : scene.getCfgEnv().getNodes() ) { + Widget nodeWidget = scene.findWidget(n); + Node node = new Node(n); + node.width=nodeWidget.getBounds().width; + node.height = nodeWidget.getBounds().height; + nodeList.add(node); + } + + for(CfgEdge e : scene.getCfgEnv().getEdges() ) { + if(e.isBackEdge()) continue; + Edge edge = new Edge(e, nodeList.getNode(e.getSourceNode().getNodeIndex()), + nodeList.getNode(e.getTargetNode().getNodeIndex())); + edgeList.add(edge); + } + + layout.visit(dg); + + for(Object obj : dg.nodes){ + Node n = (Node) obj; + CfgNode cfgNode = (CfgNode) n.data; + Point pos = new Point(n.x + LEFT_BORDER , n.y + TOP_BORDER); + Point scenepos = scene.convertLocalToScene(pos); + setResolvedNodeLocation(ug, cfgNode, scenepos); + } + + } + + @Override + protected void performNodesLayout(UniversalGraph ug, Collection collection) { + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/icons/Icons.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/icons/Icons.java new file mode 100644 index 000000000000..1fd1e0c40150 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/icons/Icons.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.icons; + +/** + * + * @author Christian Wimmer + */ +public class Icons { + private static final String PATH = "at/ssw/visualizer/cfg/icons/"; + + public static final String CFG = PATH + "cfg.gif"; + + public static final String ICON_ZOOMIN = PATH + "zoomin.gif"; + public static final String ICON_ZOOMOUT = PATH + "zoomout.gif"; + public static final String ICON_DEFAULT = PATH + "arrangebfs.gif"; + + private Icons() { + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgEdge.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgEdge.java new file mode 100644 index 000000000000..aa05ff5a9e51 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgEdge.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.model; + +public interface CfgEdge { + public CfgNode getSourceNode(); + public CfgNode getTargetNode(); + public boolean isXhandler(); + public boolean isBackEdge(); + public boolean isSymmetric(); + public boolean isReflexive(); +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgEdgeImpl.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgEdgeImpl.java new file mode 100644 index 000000000000..7ddf15b0d6fd --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgEdgeImpl.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.model; + +public class CfgEdgeImpl implements CfgEdge { + private CfgNodeImpl sourceNode; + private CfgNodeImpl targetNode; + private boolean symmetric; + private boolean backedge; + + public CfgEdgeImpl(CfgNodeImpl sourceNode, CfgNodeImpl targetNode) { + this.sourceNode = sourceNode; + this.targetNode = targetNode; + } + + public void setSymmetric(boolean symmetric){ + this.symmetric = symmetric; + } + + public void setBackEdge(boolean isBackedge){ + this.backedge = isBackedge; + } + + public CfgNode getSourceNode() { + return sourceNode; + } + + public CfgNode getTargetNode() { + return targetNode; + } + + public boolean isBackEdge() { + return this.backedge; + } + + public boolean isSymmetric() { + return symmetric; + } + + public boolean isReflexive() { + return sourceNode==targetNode; + } + + public boolean isXhandler() { + return sourceNode.getBasicBlock().getXhandlers().contains(targetNode.getBasicBlock()); + } + + @Override + public String toString(){ + return this.sourceNode.getBasicBlock().getName() + "->" + targetNode.getBasicBlock().getName(); + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgEnv.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgEnv.java new file mode 100644 index 000000000000..73d1da492233 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgEnv.java @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.model; + +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; + + +/** + * This is the container class for the data model, + * it prepares creates nodes and edges for the CfgScene + * from a ControlFlowGraph of the Compilation Model + */ +public class CfgEnv { + private ControlFlowGraph cfg; + private Map loopMap;//maps: LoopHeader --> LoopInfo + private CfgNodeImpl[] nodeArr; + private CfgEdgeImpl[] edgeArr; + + public CfgEnv(ControlFlowGraph cfg) { + this.cfg = cfg; + int blockCount = cfg.getBasicBlocks().size(); + CfgNodeImpl[] nodes = new CfgNodeImpl[blockCount]; + Map block2nodeMap = new HashMap(); + Map> inputMap = new HashMap>(); + ArrayList allEdges = new ArrayList(); + List blocks = cfg.getBasicBlocks(); + //create nodes + for(int idx=0 ; idx < blockCount ; idx++) { + BasicBlock b = blocks.get(idx); + + String description = "Name: " + b.getName() + "\n"; + description += "BCI: [" + b.getFromBci() + "," + b.getToBci() + "]\n"; + if (b.getLoopDepth() > 0) { + description += "Loop " + b.getLoopIndex() + " Depth " + b.getLoopDepth() + "\n"; + } + description += "Predecessors: " + getBlockList(b.getPredecessors()) + "\n"; + description += "Successors: " + getBlockList(b.getSuccessors()) + "\n"; + description += "XHandlers: " + getBlockList(b.getXhandlers()); + if (b.getDominator() != null) { + description += "\nDominator: " + b.getDominator().getName(); + } + nodes[idx] = new CfgNodeImpl(b, idx, description); + block2nodeMap.put(b, nodes[idx]); + } + + + //create edges + Set cache = new HashSet();//avoids identical edges with same source and same target + for(int i = 0 ; i < blockCount ; i++) { + BasicBlock b = blocks.get(i); + List outputEdges = new ArrayList(); + + Set successors = new HashSet(); + successors.addAll(b.getSuccessors()); + successors.addAll(b.getXhandlers()); + for(BasicBlock sb : successors) { + CfgNodeImpl succNode = block2nodeMap.get(sb); + CfgEdgeImpl edge = new CfgEdgeImpl(nodes[i], succNode); + if(cache.contains(edge.toString())) + continue; + cache.add(edge.toString()); + //check for symtric edges + if(sb.getXhandlers().contains(b) || sb.getSuccessors().contains(b)) + edge.setSymmetric(true); + outputEdges.add(edge); + } + allEdges.addAll(outputEdges); + nodes[i].setOutputEdges(outputEdges.toArray(new CfgEdgeImpl[outputEdges.size()])); + } + + for(CfgEdgeImpl e: allEdges) { + //CfgNodeImpl src = (CfgNodeImpl) e.getSourceNode(); + CfgNodeImpl tar = (CfgNodeImpl) e.getTargetNode(); + Set set = inputMap.get(tar); + if( set == null) { + set = new HashSet(); + set.add(e); + inputMap.put(tar, set); + } + set.add(e); + } + for(CfgNodeImpl n : nodes){ + Set inputEdges = inputMap.get(n); + if(inputEdges == null) continue; + n.setInputEdges(inputEdges.toArray(new CfgEdgeImpl[inputEdges.size()])); + } + CfgEdgeImpl[] edges = allEdges.toArray(new CfgEdgeImpl[allEdges.size()]); + this.edgeArr=edges; + this.nodeArr=nodes; + CfgNodeImpl rootNode = nodeArr[0]; + setNodeLevels(rootNode); + indexLoops(rootNode); + + } + + + private String getBlockList(List blocks) { + if (blocks.size() == 0) { + return "None"; + } + StringBuilder sb = new StringBuilder(); + String prefix = ""; + for (BasicBlock b : blocks) { + sb.append(prefix).append(b.getName()); + prefix = ", "; + } + return sb.toString(); + } + + + public CfgNode[] getNodes(){ + return this.nodeArr; + } + + public CfgEdge[] getEdges(){ + return this.edgeArr; + } + + public Map getLoopMap() { + return loopMap; + } + + public void setLoopMap(Map loopMap) { + this.loopMap = loopMap; + } + + private void indexLoops(CfgNodeImpl rootNode){ + LoopEnv env = new LoopEnv(Arrays.asList(nodeArr)); + loopDetection(env, rootNode); + calcLoopDepth(env); + + int loopIndex=1; + + for(LoopInfo info : env.loopMap.values()) { + info.setLoopIndex(loopIndex++); + info.setLoopDepth(info.getHeader().getLoopDepth()); + for(CfgNode n : info.getMembers()){ + if(n.getLoopDepth()>info.getLoopDepth()) continue; + CfgNodeImpl ni = (CfgNodeImpl) n; + ni.setLoopDepth(info.getLoopDepth()); + ni.setLoopIndex(info.getLoopIndex()); + } + } + + for(LoopInfo info : env.loopMap.values()) { + HashSet members = new HashSet(info.getMembers()); + members.remove(info.getHeader());//remove own header + for(CfgNode n: members){ + if(n.isLoopHeader()) { + LoopInfo memberInfo = env.loopMap.get(n); + if (info.getLoopDepth() == memberInfo.getLoopDepth()-1) + memberInfo.setParent(info); + } + } + } + this.loopMap = env.loopMap; + } + + + private class LoopEnv { + Set allNodes; + Set activeNodes; + Set visitedNodes; + Map loopMap; + private int loopIndex=0; + + public LoopEnv(Collection nodes){ + allNodes = new HashSet(nodes); + activeNodes = new HashSet(2 * allNodes.size()); + visitedNodes = new HashSet(2 * allNodes.size()); + loopMap = new HashMap(); + } + + public int getLoopIndex(){ + return ++loopIndex; + } + } + + + private void loopDetection(LoopEnv env, CfgNodeImpl root) { + for (CfgNodeImpl n : env.allNodes) { + n.setLoopHeader(false); + for (CfgEdge e : n.getInputEdges()) { + CfgEdgeImpl ei = (CfgEdgeImpl) e; + ei.setBackEdge(false); + } + } + visit(env, root, null); + } + + + + private void visit(LoopEnv env, CfgNodeImpl n, CfgEdgeImpl e) { + if (env.activeNodes.contains(n)) { + // This node is b loop header! + n.setLoopHeader(true); + e.setBackEdge(true); + } else if (!env.visitedNodes.contains(n)) { + env.visitedNodes.add(n); + env.activeNodes.add(n); + + for (CfgEdge edge : n.getOutputEdges()) { + if(!edge.getTargetNode().isOSR()) { + CfgEdgeImpl ei = (CfgEdgeImpl) edge; + CfgNodeImpl ni = (CfgNodeImpl) edge.getTargetNode(); + visit(env, ni, ei); + } + } + env.activeNodes.remove(n); + } + } + + + + private void calcLoopDepth(LoopEnv env) { + for (CfgNodeImpl n : env.allNodes) { + env.visitedNodes.clear(); + + if (n.isLoopHeader()) { + LoopInfo loop = new LoopInfo(); + loop.setHeader(n); + n.setLoopIndex(env.getLoopIndex()); + HashSet members = new HashSet(); + loop.setMembers(members); + members.add(n); + env.loopMap.put(loop.getHeader(), loop); + int loopDepth = n.getLoopDepth() + 1; + loop.setLoopDepth(loopDepth); + n.setLoopDepth(loopDepth); + for (CfgEdge e : n.getInputEdges()) { + if (e.isBackEdge() && !e.getSourceNode().isOSR()) { + CfgNodeImpl src = (CfgNodeImpl) e.getSourceNode(); + backwardIteration(env, n, src, loop); + loop.getBackEdges().add(e); + } + } + } + } + } + + + private void backwardIteration(LoopEnv env, CfgNodeImpl endNode, CfgNodeImpl n, LoopInfo loop) { + if (endNode != n && !env.visitedNodes.contains(n)) { + env.visitedNodes.add(n); + + for (CfgEdge e : n.getInputEdges()) { + if (!e.getSourceNode().isOSR()) { + CfgNodeImpl src = (CfgNodeImpl) e.getSourceNode(); + backwardIteration(env, endNode, src, loop); + } + } + loop.getMembers().add(n); + n.setLoopDepth(n.getLoopDepth() + 1); + } + } + + private void setNodeLevels(CfgNode rootNode){ + Set cache = new HashSet(); + Queue queue = new LinkedList(); + queue.add(rootNode); + cache.add(rootNode); + int level=0; + int[] nodeCount = new int[2]; + nodeCount[0]=1; + while(!queue.isEmpty()){ + CfgNodeImpl curNode = (CfgNodeImpl) queue.poll(); + curNode.setLevel(level); + nodeCount[0]--; + for(CfgEdge outEdge : curNode.getOutputEdges()) { + CfgNode succNode = outEdge.getTargetNode(); + if(cache.contains(succNode)) continue; + cache.add(succNode); + queue.add(succNode); + nodeCount[1]++; + } + if(nodeCount[0]==0){ + nodeCount[0]=nodeCount[1]; + nodeCount[1]=0; + level++; + } + } + } + + public ControlFlowGraph getCfg() { + return cfg; + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgNode.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgNode.java new file mode 100644 index 000000000000..0a7b1b93a09f --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgNode.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.model; + +import at.ssw.visualizer.model.cfg.BasicBlock; + + +public interface CfgNode { + //testers + public boolean isOSR(); + public boolean isRoot(); + public boolean isLoopHeader(); + public boolean isLoopMember(); + + //getters + public int getLevel(); + public int getLoopDepth(); + public int getLoopIndex(); + public int getNodeIndex(); + public CfgEdge[] getInputEdges(); + public CfgEdge[] getOutputEdges(); + public BasicBlock getBasicBlock(); + public String getDescription(); + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgNodeImpl.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgNodeImpl.java new file mode 100644 index 000000000000..b55d203aa65d --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/CfgNodeImpl.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.model; + +import at.ssw.visualizer.model.cfg.BasicBlock; +import java.awt.Color; + + + +public class CfgNodeImpl implements CfgNode { + private int nodeIndex; + private BasicBlock basicBlock; + private int level; + private int loopDepth=0; + private int loopIndex=0; + private boolean osr=false; + private CfgNodeImpl dominator=null; + private CfgEdgeImpl[] inputEdges = new CfgEdgeImpl[0]; + private CfgEdgeImpl[] outputEdges = new CfgEdgeImpl[0]; + private String description; + private Color customColor=null; + private boolean loopHeader; + + public CfgNodeImpl(BasicBlock bb, int nodeIndex, String description) { + this.basicBlock = bb; + this.nodeIndex = nodeIndex; + this.description = description; + + if (bb.getPredecessors().size() == 1) { + BasicBlock pred = bb.getPredecessors().get(0); + boolean isStd = pred.getPredecessors().size() == 0; + if (isStd) { + for (String s : bb.getFlags()) { + if (s.equals("osr")) { + osr = true; + break; + } + } + } + } + } + + public int getNodeIndex() { + return nodeIndex; + } + + + public void setDominator(CfgNodeImpl dominator) { + this.dominator = dominator; + } + + public void setLevel(int level) { + this.level = level; + } + + public void setLoopDepth(int loopDepth) { + this.loopDepth = loopDepth; + } + + public void setLoopHeader(boolean loopHeader) { + this.loopHeader = loopHeader; + } + + public void setLoopIndex(int loopIndex) { + this.loopIndex = loopIndex; + } + + public void setNodeIndex(int nodeIndex) { + this.nodeIndex = nodeIndex; + } + + public boolean isRoot() { + return nodeIndex==0; + } + + public boolean isLoopHeader() { + return loopHeader; + } + + public boolean isLoopMember() { + return loopIndex > 0; + } + + public int getLevel() { + return level; + } + + public BasicBlock getBasicBlock() { + return basicBlock; + } + + public int getLoopDepth() { + return loopDepth; + } + + public int getLoopIndex() { + return loopIndex; + } + + public boolean isOSR() { + return osr; + } + + @Override + public String toString(){ + return basicBlock.getName(); + } + + public void setInputEdges(CfgEdgeImpl[] inputEdges){ + this.inputEdges = inputEdges; + } + + + public void setOutputEdges(CfgEdgeImpl[] outputEdges){ + this.outputEdges = outputEdges; + } + + public CfgEdge[] getInputEdges() { + return this.inputEdges; + } + + public CfgEdge[] getOutputEdges() { + return outputEdges; + } + + public String getDescription() { + return description; + } + + public void setColor(Color color) { + this.customColor=color; + } + + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/LoopInfo.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/LoopInfo.java new file mode 100644 index 000000000000..26d036a4fde7 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/model/LoopInfo.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + + +public class LoopInfo { + private CfgNode header;//the target node of the backedge + private int loopIndex; //index of the min cycleSet + private int loopDepth; //nested depth >=1 + private LoopInfo parent=null; + private Set members; + private List backEdges = new ArrayList();//dfs backEdge + + protected void setLoopDepth(int depth) { + this.loopDepth=depth; + } + + protected void setLoopIndex(int loopIndex) { + this.loopIndex = loopIndex; + } + + public int getLoopDepth() { + return loopDepth; + } + + public Set getMembers() { + return members; + } + + protected void setMembers(Set members) { + this.members = members; + } + + public int getLoopIndex() { + return loopIndex; + } + + protected void setParent(LoopInfo parent) { + this.parent = parent; + } + + public LoopInfo getParent(){ + return parent; + } + + public List getBackEdges() { + return backEdges; + } + + public CfgNode getHeader() { + return header; + } + + protected void setHeader(CfgNode header) { + this.header = header; + } + + @Override + public String toString(){ + return "Loop(" + header.toString()+ ")-->" + members.toString(); + } + + + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CFGOptionsCategory.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CFGOptionsCategory.java new file mode 100644 index 000000000000..0a8f42fc7e5b --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CFGOptionsCategory.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.preferences; + +import javax.swing.Icon; +import javax.swing.ImageIcon; +import org.netbeans.spi.options.OptionsCategory; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.ImageUtilities; + +/** + * Descriptor for the settings page displayed in the options dialog. + * + * @author Bernhard Stiftner + */ +public class CFGOptionsCategory extends OptionsCategory { + + public OptionsPanelController create() { + return new CFGOptionsPanelController(); + } + + public String getCategoryName() { + return "Control Flow Graph"; + } + + @Override + public Icon getIcon() { + return new ImageIcon(ImageUtilities.loadImage("at/ssw/visualizer/cfg/icons/cfg32.gif")); + } + + public String getTitle() { + return "CFG Visualizer"; + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CFGOptionsPanel.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CFGOptionsPanel.java new file mode 100644 index 000000000000..ea550622451a --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CFGOptionsPanel.java @@ -0,0 +1,504 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.preferences; + +import java.awt.Color; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; +import javax.swing.AbstractAction; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.SwingConstants; + +/** + * The actual component for changing the CFG visualizer settings. + * + * @author Bernhard Stiftner + * @author Rumpfhuber Stefan + */ +public class CFGOptionsPanel extends JPanel { + + List elements = new ArrayList(); + + /** Creates a new instance of CFGOptionsPanel */ + public CFGOptionsPanel() { + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + JPanel cfgPanel = new JPanel(new GridBagLayout()); + + // color/font settings + addColorChooser(cfgPanel, "Background color: ", new ColorChooser(CfgPreferences.PROP_BACKGROUND_COLOR){ + @Override + public void apply() { + CfgPreferences.getInstance().setBackgroundColor(getColor()); + } + + @Override + public void update() { + this.originalColor = CfgPreferences.getInstance().getBackgroundColor(); + setColor(this.originalColor); + } + + public void reset() { + this.setColor(CfgPreferencesDefaults.DEFAULT_BACKGROUND_COLOR); + } + + + + }); + addColorChooser(cfgPanel, "Back edge color: ", new ColorChooser(CfgPreferences.PROP_BACK_EDGE_COLOR){ + + @Override + public void apply() { + CfgPreferences.getInstance().setBackedgeColor(getColor()); + } + + @Override + public void update() { + this.originalColor = CfgPreferences.getInstance().getBackedgeColor(); + this.setColor(this.originalColor); + + } + + public void reset() { + this.setColor(CfgPreferencesDefaults.DEFAULT_BACKEDGE_COLOR); + } + }); + addColorChooser(cfgPanel, "Edge color: ", new ColorChooser(CfgPreferences.PROP_EDGE_COLOR){ + + @Override + public void apply() { + CfgPreferences.getInstance().setEdgeColor(getColor()); + } + + @Override + public void update() { + this.originalColor = CfgPreferences.getInstance().getEdgeColor(); + setColor(this.originalColor); + } + + public void reset() { + this.setColor(CfgPreferencesDefaults.DEFAULT_EDGE_COLOR); + } + + }); + addColorChooser(cfgPanel, "Exception edge color: ", new ColorChooser(CfgPreferences.PROP_EXCEPTION_EDGE_COLOR){ + + @Override + public void apply() { + CfgPreferences.getInstance().setExceptionEdgeColor(getColor()); + } + + @Override + public void update() { + this.originalColor = CfgPreferences.getInstance().getExceptionEdgeColor(); + setColor(this.originalColor); + + } + + public void reset() { + this.setColor(CfgPreferencesDefaults.DEFAULT_EXCEPTIONEDGE_COLOR); + } + }); + addColorChooser(cfgPanel, "Node color: ", new ColorChooser(CfgPreferences.PROP_NODE_COLOR){ + + @Override + public void apply() { + CfgPreferences.getInstance().setNodeColor(getColor()); + } + + @Override + public void update() { + this.originalColor = CfgPreferences.getInstance().getNodeColor(); + setColor(this.originalColor); + + } + + public void reset() { + this.setColor(CfgPreferencesDefaults.DEFAUT_NODE_COLOR); + } + }); + addColorChooser(cfgPanel, "Text color: ", new ColorChooser(CfgPreferences.PROP_TEXT_COLOR){ + + @Override + public void apply() { + CfgPreferences.getInstance().setTextColor(getColor()); + } + + @Override + public void update() { + this.originalColor = CfgPreferences.getInstance().getTextColor(); + + } + + public void reset() { + this.setColor(CfgPreferencesDefaults.DEFAULT_TEXT_COLOR); + } + }); + addColorChooser(cfgPanel, "Border color: ", new ColorChooser(CfgPreferences.PROP_BORDER_COLOR){ + + @Override + public void apply() { + CfgPreferences.getInstance().setBorderColor(getColor()); + } + + @Override + public void update() { + this.originalColor = CfgPreferences.getInstance().getBorderColor(); + setColor(this.originalColor); + + } + public void reset() { + this.setColor(CfgPreferencesDefaults.DEFAULT_BORDER_COLOR); + } + }); + addColorChooser(cfgPanel, "Selected Nodes color: ", new ColorChooser(CfgPreferences.PROP_SELECTION_COLOR_FG){ + + @Override + public void apply() { + CfgPreferences.getInstance().setSelectionColorForeground(getColor()); + } + + @Override + public void update() { + this.originalColor = CfgPreferences.getInstance().getSelectionColorForeground(); + setColor(this.originalColor); + } + + public void reset() { + this.setColor(CfgPreferencesDefaults.DEFAULT_SELECTION_COLOR_FOREGROUND); + } + }); + addColorChooser(cfgPanel, "Selection Rect color: ", new ColorChooser(CfgPreferences.PROP_SELECTION_COLOR_BG){ + + @Override + public void apply() { + CfgPreferences.getInstance().setSelectionColorBackground(getColor()); + } + + @Override + public void update() { + this.originalColor = CfgPreferences.getInstance().getSelectionColorBackground(); + setColor(this.originalColor); + } + + public void reset() { + this.setColor(CfgPreferencesDefaults.DEFAULT_SELECTION_COLOR_BACKGROUND); + } + }); + addFontChooser(cfgPanel, "Text font: ", new FontChooser(CfgPreferences.PROP_TEXT_FONT){ + + @Override + public void apply() { + CfgPreferences.getInstance().setTextFont(getSelectedFont()); + } + + @Override + public void update() { + this.originalFont = CfgPreferences.getInstance().getTextFont(); + this.setSelectedFont(originalFont); + + } + + public void reset() { + this.setSelectedFont(CfgPreferencesDefaults.DEFAULT_TEXT_FONT); + } + }); + + // flags editor + addFlagsEditor(cfgPanel, "Flags: "); + add(cfgPanel); + + // add update button + Box hBox = new Box(BoxLayout.X_AXIS); + hBox.add(new JButton(new ResetAction())); + hBox.add(Box.createHorizontalGlue()); + add(hBox); + + add(Box.createVerticalGlue()); + } + + public void update() { + for (ConfigurationElement e : elements) { + e.update(); + } + } + + public void cancel() { + for (ConfigurationElement e : elements) { + e.update(); + } + } + + public void applyChanges() { + for (ConfigurationElement e : elements) { + if(e.isChanged()) + e.apply(); + } + } + + public boolean isDataValid() { + return true; + } + + public boolean isChanged() { + for (ConfigurationElement e : elements) { + if (e.isChanged()) { + return true; + } + } + return false; + } + + public void loadDefault() { + for (ConfigurationElement e : elements) { + e.reset(); + } + } + + private void addColorChooser(JComponent c, String displayName, ColorChooser chooser) { + GridBagLayout layout = (GridBagLayout)c.getLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.insets = new Insets(3, 3, 3, 3); + constraints.weightx = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.EAST; + JLabel label = new JLabel(displayName, JLabel.TRAILING); + layout.setConstraints(label, constraints); + c.add(label); + constraints.anchor = GridBagConstraints.WEST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + layout.setConstraints(chooser, constraints); + c.add(chooser); + elements.add(chooser); + label.setLabelFor(chooser); + } + + + + private void addFontChooser(JComponent c, String displayName, FontChooser chooser) { + GridBagLayout layout = (GridBagLayout)c.getLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.insets = new Insets(3, 3, 3, 3); + constraints.weightx = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.EAST; + JLabel label = new JLabel(displayName, JLabel.TRAILING); + layout.setConstraints(label, constraints); + c.add(label); + constraints.insets = new Insets(3, 3, 3, 1); + constraints.weightx = 1.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.anchor = GridBagConstraints.CENTER; + layout.setConstraints(chooser.getPreview(), constraints); + c.add(chooser.getPreview()); + constraints.insets = new Insets(3, 1, 3, 3); + constraints.weightx = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.EAST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + layout.setConstraints(chooser.getButton(), constraints); + c.add(chooser.getButton()); + elements.add(chooser); + label.setLabelFor(chooser.getButton()); + } + + private void addFlagsEditor(JComponent c, String displayName) { + GridBagLayout layout = (GridBagLayout)c.getLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.insets = new Insets(3, 3, 3, 3); + constraints.weightx = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHEAST; + FlagsEditor flagsEditor = new FlagsEditor(); + JLabel flagsLabel = new JLabel(displayName, JLabel.TRAILING); + flagsLabel.setVerticalAlignment(SwingConstants.TOP); + layout.setConstraints(flagsLabel, constraints); + c.add(flagsLabel); + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.fill = GridBagConstraints.BOTH; + constraints.anchor = GridBagConstraints.CENTER; + constraints.gridwidth = GridBagConstraints.REMAINDER; + layout.setConstraints(flagsEditor, constraints); + c.add(flagsEditor); + elements.add(flagsEditor); + flagsLabel.setLabelFor(flagsEditor); + } + + + + + interface ConfigurationElement { + + public boolean isChanged(); + + //apply changes to preferences + public void apply(); + + //optain current value from preferences + public void update(); + + //reset to default value + public void reset(); + + } + + abstract class ColorChooser extends ColorChooserButton implements ConfigurationElement { + + Color originalColor;//the color before any change + String propertyName; + + public ColorChooser(String propertyName) { + this.propertyName = propertyName; + } + + public boolean isChanged() { + return !originalColor.equals(getColor()); + } + + + public abstract void apply(); + public abstract void update(); + + } + + + abstract class FontChooser implements ConfigurationElement, ActionListener { + + Font originalFont; + Font selectedFont; + String propertyName; + JTextField preview; + JButton button; + + public FontChooser(String propertyName) { + this.propertyName = propertyName; + preview = new JTextField(""); + preview.setEditable(false); + button = new JButton("..."); + button.setMargin(new Insets(0, 0, 0, 0)); + button.addActionListener(this); + } + + public boolean isChanged() { + return !originalFont.equals(selectedFont); + } + + public abstract void apply(); + + public abstract void update(); + + public abstract void reset(); + + public JTextField getPreview() { + return preview; + } + + public JButton getButton() { + return button; + }; + + public Font getSelectedFont() { + return selectedFont; + } + + public void setSelectedFont(Font f) { + selectedFont = f; + preview.setText(fontToString(f)); + preview.revalidate(); + } + + public void actionPerformed(ActionEvent e) { + setSelectedFont(FontChooserDialog.show(selectedFont)); + } + + public String fontToString(Font font) { + StringBuffer sb = new StringBuffer(); + sb.append(font.getName()); + sb.append(" "); + sb.append(font.getSize()); + if (font.isBold()) { + sb.append(" bold"); + } + if (font.isItalic()) { + sb.append(" italic"); + } + return sb.toString(); + } + + } + + class FlagsEditor extends FlagsEditorPanel implements ConfigurationElement { + + FlagsSetting originalFlags; + + public FlagsEditor() { + super(null); + } + + public boolean isChanged() { + return !originalFlags.getFlagString().equals(getFlagString()); + } + + public void apply() { + CfgPreferences.getInstance().setFlagsSetting(new FlagsSetting(getFlagString())); + update(); + } + + public void update() { + originalFlags = CfgPreferences.getInstance().getFlagsSetting(); + setFlagString(originalFlags.getFlagString()); + } + + public void reset() { + FlagsSetting defaultFlags = new FlagsSetting(CfgPreferencesDefaults.DEFAULT_FLAGSTRING); + setFlagString(defaultFlags.getFlagString()); + colorButton.setColor(getParent().getBackground()); + } + + } + + class ResetAction extends AbstractAction { + + public ResetAction() { + super("Reset"); + } + + public void actionPerformed(ActionEvent e) { + CFGOptionsPanel.this.loadDefault(); + } + + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CFGOptionsPanelController.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CFGOptionsPanelController.java new file mode 100644 index 000000000000..bb5fcc032409 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CFGOptionsPanelController.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.preferences; + +import java.beans.PropertyChangeListener; +import javax.swing.JComponent; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.HelpCtx; +import org.openide.util.Lookup; + +/** + * Controller for the settings page displayed in the options dialog. + * + * @author Bernhard Stiftner + */ +public class CFGOptionsPanelController extends OptionsPanelController { + + CFGOptionsPanel optionsPanel; + + + public void update() { + getOptionsPanel().update(); + } + + public void applyChanges() { + getOptionsPanel().applyChanges(); + } + + public void cancel() { + getOptionsPanel().cancel(); + } + + public boolean isValid() { + return getOptionsPanel().isDataValid(); + } + + public boolean isChanged() { + return getOptionsPanel().isChanged(); + } + + public JComponent getComponent(Lookup masterLookup) { + return getOptionsPanel(); + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + public void addPropertyChangeListener(PropertyChangeListener l) { + getOptionsPanel().addPropertyChangeListener(l); + } + + //todo: investigate - who removes the changelistener ? + public void removePropertyChangeListener(PropertyChangeListener l) { + getOptionsPanel().removePropertyChangeListener(l); + } + + private CFGOptionsPanel getOptionsPanel() { + if (optionsPanel == null) { + optionsPanel = new CFGOptionsPanel(); + } + return optionsPanel; + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CfgPreferences.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CfgPreferences.java new file mode 100644 index 000000000000..a44e216b1f33 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CfgPreferences.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.preferences; + +import at.ssw.visualizer.cfg.editor.CfgEditorTopComponent; +import java.awt.Color; +import java.awt.Font; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.prefs.Preferences; +import javax.swing.event.EventListenerList; +import org.openide.util.NbPreferences; + +/** + * Replacement for old CFGSettings to remove dependency on deprecated SystemOption package + * + * @author Rumpfhuber Stefan + */ +public class CfgPreferences { + public static final String PROP_FLAGS = "flagsPreference"; + public static final String PROP_TEXT_FONT = "textFontPreference"; + public static final String PROP_TEXT_COLOR = "textColorPreference"; + public static final String PROP_NODE_COLOR = "nodeColorPreference"; + public static final String PROP_EDGE_COLOR = "edgeColorPreference"; + public static final String PROP_BORDER_COLOR = "borderColorPreference"; + public static final String PROP_BACK_EDGE_COLOR = "backEdgeColorPreference"; + public static final String PROP_BACKGROUND_COLOR = "backgroundColorPreference"; + public static final String PROP_SELECTION_COLOR_FG = "selectionColorFgPreference"; + public static final String PROP_SELECTION_COLOR_BG = "selectionColorBgPreference"; + public static final String PROP_EXCEPTION_EDGE_COLOR = "exceptionEdgeColorPreference"; + + private static final String PROP_FONTNAME = "_FontFamily"; + private static final String PROP_FONTSIZE = "_FontSize"; + private static final String PROP_FONTSTYLE = "_FontStyle"; + + protected static final String nodeName = "CfgPreferences"; + + private static CfgPreferences instance = new CfgPreferences(); + private EventListenerList listenerList; + + private FlagsSetting flagsSetting; + private Color node_color; + private Color background_color; + private Color backedge_color; + private Color edge_color; + private Color border_color; + private Color exceptionEdgeColor; + private Color text_color; + private Font text_font; + private Color selection_color_fg; + private Color selection_color_bg; + + + private CfgPreferences(){ + listenerList = new EventListenerList(); + init(); + } + + public static CfgPreferences getInstance(){ + return instance; + } + + public void addPropertyChangeListener(CfgEditorTopComponent listener) { + listenerList.add(PropertyChangeListener.class, listener); + } + + public void removePropertyChangeListener(CfgEditorTopComponent listener) { + listenerList.remove(PropertyChangeListener.class, listener); + } + + protected final Preferences getPreferences() { + return NbPreferences.forModule(this.getClass()).node("options").node(nodeName); + } + + + protected void init(){ + Preferences prefs = this.getPreferences(); + String flagString = prefs.get(PROP_FLAGS, CfgPreferencesDefaults.DEFAULT_FLAGSTRING); + flagsSetting = new FlagsSetting(flagString); + node_color = this.getColorProperty(PROP_NODE_COLOR, CfgPreferencesDefaults.DEFAUT_NODE_COLOR); + background_color = this.getColorProperty(PROP_BACKGROUND_COLOR, CfgPreferencesDefaults.DEFAULT_BACKGROUND_COLOR); + backedge_color = this.getColorProperty(PROP_BACK_EDGE_COLOR, CfgPreferencesDefaults.DEFAULT_BACKEDGE_COLOR); + edge_color = this.getColorProperty(PROP_EDGE_COLOR, CfgPreferencesDefaults.DEFAULT_EDGE_COLOR); + selection_color_fg= this.getColorProperty(PROP_SELECTION_COLOR_FG, CfgPreferencesDefaults.DEFAULT_SELECTION_COLOR_FOREGROUND); + border_color = this.getColorProperty(PROP_BORDER_COLOR, CfgPreferencesDefaults.DEFAULT_BORDER_COLOR); + exceptionEdgeColor = this.getColorProperty(PROP_EXCEPTION_EDGE_COLOR, CfgPreferencesDefaults.DEFAULT_EXCEPTIONEDGE_COLOR); + text_color= this.getColorProperty(PROP_TEXT_COLOR, CfgPreferencesDefaults.DEFAULT_TEXT_COLOR); + selection_color_bg = this.getColorProperty(PROP_SELECTION_COLOR_BG, CfgPreferencesDefaults.DEFAULT_SELECTION_COLOR_BACKGROUND); + selection_color_fg = this.getColorProperty(PROP_SELECTION_COLOR_FG, CfgPreferencesDefaults.DEFAULT_SELECTION_COLOR_FOREGROUND); + text_font = this.getFontProperty(PROP_TEXT_FONT, CfgPreferencesDefaults.DEFAULT_TEXT_FONT); + } + + private void firePropertyChange(String propertyName, Object oldValue, Object newValue) { + Object[] listeners = listenerList.getListenerList(); + + PropertyChangeEvent event = new PropertyChangeEvent(this, propertyName, oldValue, newValue); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == PropertyChangeListener.class) { + ((PropertyChangeListener) listeners[i+1]).propertyChange(event); + } + } + } + + private Font getFontProperty(String propName, Font defaultFont){ + Preferences prefs = this.getPreferences(); + String fontName = prefs.get(propName+PROP_FONTNAME, defaultFont.getFamily()); + int fontSize = prefs.getInt(propName+PROP_FONTSIZE, defaultFont.getSize()); + int fontStyle = prefs.getInt(propName+PROP_FONTSTYLE, defaultFont.getStyle()); + return new Font(fontName, fontStyle, fontSize); + } + + private Color getColorProperty(String propName, Color defaultColor){ + Preferences prefs = this.getPreferences(); + int srgb = prefs.getInt(propName, defaultColor.getRGB()); + if(srgb == defaultColor.getRGB()) + return defaultColor; + return new Color(srgb); + } + + public Color getBackedgeColor() { + return backedge_color; + } + + public Color getBackgroundColor() { + return background_color; + } + + public Color getBorderColor() { + return border_color; + } + + public Color getEdgeColor() { + return edge_color; + } + + public Color getExceptionEdgeColor() { + return exceptionEdgeColor; + } + + public Color getNodeColor() { + return node_color; + } + + public Color getSelectionColorForeground() { + return selection_color_fg; + } + + public Color getSelectionColorBackground() { + return selection_color_bg; + } + + public Color getTextColor() { + return text_color; + } + + public Font getTextFont() { + return text_font; + } + + public FlagsSetting getFlagsSetting() { + return flagsSetting; + } + + + public void setFlagsSetting(FlagsSetting flagsSetting) { + FlagsSetting old = this.getFlagsSetting(); + this.flagsSetting = flagsSetting; + Preferences prefs = getPreferences(); + firePropertyChange(PROP_FLAGS, old, flagsSetting); + prefs.put(PROP_FLAGS, flagsSetting.getFlagString()); + } + + + public void setTextFont(Font text_font) { + Font old = this.getTextFont(); + Preferences prefs = getPreferences(); + this.text_font = text_font; + firePropertyChange(PROP_TEXT_FONT, old, text_font); + prefs.put(PROP_TEXT_FONT + PROP_FONTNAME , text_font.getFamily()); + prefs.putInt(PROP_TEXT_FONT + PROP_FONTSIZE, text_font.getSize()); + prefs.putInt(PROP_TEXT_FONT + PROP_FONTSTYLE, text_font.getStyle()); + } + + public void setBackedgeColor(Color backedge_color) { + Color old = this.getBackedgeColor(); + this.backedge_color = backedge_color; + firePropertyChange(PROP_BACK_EDGE_COLOR, old, backedge_color); + getPreferences().putInt(PROP_BACK_EDGE_COLOR, backedge_color.getRGB()); + } + + public void setBackgroundColor(Color bg_color) { + Color old = this.getBackgroundColor(); + background_color = bg_color; + firePropertyChange(PROP_BACKGROUND_COLOR, old, bg_color ); + getPreferences().putInt(PROP_BACKGROUND_COLOR, bg_color.getRGB()); + } + + public void setBorderColor(Color border_color) { + Color old = getBorderColor(); + this.border_color = border_color; + firePropertyChange(PROP_BORDER_COLOR, old, border_color ); + getPreferences().putInt(PROP_BORDER_COLOR, border_color.getRGB()); + } + + public void setEdgeColor(Color edge_color) { + Color old = getEdgeColor(); + this.edge_color = edge_color; + firePropertyChange(PROP_EDGE_COLOR, old, edge_color); + getPreferences().putInt(PROP_EDGE_COLOR, edge_color.getRGB()); + } + + public void setNodeColor(Color node_color) { + Color old = getNodeColor(); + this.node_color = node_color; + firePropertyChange(PROP_NODE_COLOR, old, node_color); + getPreferences().putInt(PROP_NODE_COLOR, node_color.getRGB()); + + } + + public void setSelectionColorForeground(Color selection_color) { + Color old = this.getSelectionColorForeground(); + this.selection_color_fg = selection_color; + firePropertyChange(PROP_SELECTION_COLOR_FG, old, selection_color); + getPreferences().putInt(PROP_SELECTION_COLOR_FG, selection_color.getRGB()); + } + + public void setSelectionColorBackground(Color selection_color) { + Color old = this.getSelectionColorBackground(); + this.selection_color_bg = selection_color; + firePropertyChange(PROP_SELECTION_COLOR_BG, old, selection_color); + getPreferences().putInt(PROP_SELECTION_COLOR_BG, selection_color.getRGB()); + } + + public void setTextColor(Color text_color) { + Color old = this.getTextColor(); + this.text_color = text_color; + firePropertyChange(PROP_TEXT_COLOR, old, text_color); + getPreferences().putInt(PROP_TEXT_COLOR, text_color.getRGB()); + } + + public void setExceptionEdgeColor(Color exceptionEdgeColor) { + Color old = this.getExceptionEdgeColor(); + this.exceptionEdgeColor = exceptionEdgeColor; + firePropertyChange(PROP_EXCEPTION_EDGE_COLOR, old, exceptionEdgeColor); + getPreferences().putInt(PROP_EXCEPTION_EDGE_COLOR, exceptionEdgeColor.getRGB()); + } + + + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CfgPreferencesDefaults.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CfgPreferencesDefaults.java new file mode 100644 index 000000000000..65288d5bc1de --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/CfgPreferencesDefaults.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.preferences; + +import java.awt.Color; +import java.awt.Font; + +/** + * Default Configuration for options panel + * + * @author Rumpfhuber Stefan + */ +public final class CfgPreferencesDefaults { + public static final String DEFAULT_FLAGSTRING = "std(224,224,128);osr(224,224,0);ex(128,128,224);sr(128,224,128);llh(224,128,128);lle(224,192,192);plh(128,224,128);bb(160,0,0);ces(192,192,192)"; + public static final Color DEFAUT_NODE_COLOR = new Color(208, 208, 208); + public static final Color DEFAULT_BACKGROUND_COLOR = new Color(255, 255, 255); + public static final Color DEFAULT_BACKEDGE_COLOR = new Color(160, 0, 0); + public static final Color DEFAULT_EDGE_COLOR = new Color(0, 0, 0); + public static final Color DEFAULT_SELECTION_COLOR_FOREGROUND = Color.BLUE; + public static final Color DEFAULT_SELECTION_COLOR_BACKGROUND = Color.BLUE; + public static final Color DEFAULT_BORDER_COLOR = new Color(0, 0, 0); + public static final Color DEFAULT_EXCEPTIONEDGE_COLOR = new Color(0, 0, 160); + public static final Color DEFAULT_TEXT_COLOR = new Color(0, 0, 0); + public static final Font DEFAULT_TEXT_FONT = new Font("Dialog", Font.PLAIN, 18); +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/ColorChooserButton.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/ColorChooserButton.java new file mode 100644 index 000000000000..d16c4ecbc079 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/ColorChooserButton.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.preferences; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JColorChooser; + +/** + * A color selection button. It will preview the currently selected color as + * an icon. Clicking the button will bring up the JColorChooser dialog. + * + * @author Bernhard Stiftner + */ +public class ColorChooserButton extends JButton implements ActionListener { + + public static final Dimension ICON_SIZE = new Dimension(24, 8); + + Color color; + boolean colorChooserEnabled = true; // bring up dialog when clicked? + + + public ColorChooserButton() { + this(Color.black); + } + + public ColorChooserButton(Color defaultColor) { + setIcon(new ColorBoxIcon()); + addActionListener(this); + color = defaultColor; + Dimension size = new Dimension(ICON_SIZE); + size.width += getInsets().left + getInsets().right; + size.height += getInsets().top + getInsets().bottom; + setPreferredSize(size); + } + + public void setColor(Color newColor) { + color = newColor; + repaint(); + } + + public Color getColor() { + return color; + } + + public boolean isColorChooserEnabled() { + return colorChooserEnabled; + } + + public void setColorChooserEnabled(boolean enabled) { + this.colorChooserEnabled = enabled; + } + + public void actionPerformed(ActionEvent e) { + if (!colorChooserEnabled) { + return; + } + + Color c = JColorChooser.showDialog(this, "Choose color", color); + if (c != null) { + setColor(c); + } + } + + class ColorBoxIcon implements Icon { + + public int getIconWidth() { + return ICON_SIZE.width; + } + + public int getIconHeight() { + return ICON_SIZE.height; + } + + public void paintIcon(Component c, Graphics g, int x, int y) { + Color oldColor = g.getColor(); + g.translate(x, y); + + g.setColor(color); + g.fillRect(0, 0, ICON_SIZE.width, ICON_SIZE.height); + + g.setColor(Color.black); + g.drawRect(0, 0, ICON_SIZE.width, ICON_SIZE.height); + + g.translate(-x, -y); + g.setColor(oldColor); + } + } +} + diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/FlagsEditorPanel.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/FlagsEditorPanel.java new file mode 100644 index 000000000000..b20868c0796f --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/FlagsEditorPanel.java @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.preferences; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Enumeration; +import java.util.StringTokenizer; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JColorChooser; +import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +/** + * + * @author Bernhard Stiftner + * @author Rumpfhuber Stefan + */ +public class FlagsEditorPanel extends JPanel implements ActionListener, + ListSelectionListener { + + FlagListModel listModel; + JList list; + ColorChooserButton colorButton; + JButton newButton; + JButton removeButton; + JButton upButton; + JButton downButton; + + + /** Creates a new instance of FlagsEditorPanel */ + public FlagsEditorPanel(String flagString) { + setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + + listModel = new FlagListModel(flagString); + list = new JList(listModel); + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + list.addListSelectionListener(this); + add(new JScrollPane(list)); + + add(Box.createHorizontalStrut(3)); + + Box buttonBox = new Box(BoxLayout.Y_AXIS); + buttonBox.add(colorButton = new ColorChooserButton()); + buttonBox.add(newButton = new JButton("New...")); + buttonBox.add(removeButton = new JButton("Remove")); + buttonBox.add(upButton = new JButton("Up")); + buttonBox.add(downButton = new JButton("Down")); + buttonBox.add(Box.createVerticalGlue()); + add(buttonBox); + layoutButtonContainer(buttonBox); + + colorButton.setColorChooserEnabled(false); + colorButton.addActionListener(this); + newButton.addActionListener(this); + removeButton.addActionListener(this); + upButton.addActionListener(this); + downButton.addActionListener(this); + + selectionChanged(-1); // no selection + } + + /** + * Ugly helper to make a nice layout for vertically aligned buttons. + */ + private static void layoutButtonContainer(JComponent buttonContainer) { + int width = 0; + int height = 0; + + for (int i=0; i= listModel.size()-1) { + return; + } + Object o = listModel.getElementAt(index); + listModel.removeElementAt(index); + listModel.insertElementAt(o, index+1); + } + } + + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()) { + return; // another event will be fired soon + } + selectionChanged(list.getSelectedIndex()); + } + + protected void selectionChanged(int index) { //index is -1 if there is no selection + colorButton.setEnabled(index >= 0); + removeButton.setEnabled(index >= 0); + upButton.setEnabled(index > 0); + downButton.setEnabled(index >= 0 && index < listModel.getSize()-1); + + if (index >= 0) { + FlagListItem item = (FlagListItem)listModel.elementAt(index); + colorButton.setColor(item.getColor()); + list.setSelectedIndex(index); + } else { + colorButton.setColor(getBackground()); + } + } + + protected void changeColor() { + int selectedIndex = list.getSelectedIndex(); + FlagListItem item = (FlagListItem)listModel.elementAt(selectedIndex); + Color c = JColorChooser.showDialog(this, "Choose color", item.getColor()); + + if (c != null) { + item.setColor(c); + colorButton.setColor(c); + } + } + + class FlagListModel extends DefaultListModel { + + public FlagListModel(String flagString) { + if (flagString != null) { + setFlagString(flagString); + } + } + + public String getFlagString() { + StringBuffer sb = new StringBuffer(); + Enumeration e = elements(); + while (e.hasMoreElements()) { + FlagListItem item = (FlagListItem)e.nextElement(); + sb.append(item.getFlagString()); + Color c = item.getColor(); + sb.append("(").append(c.getRed()).append(",").append(c.getGreen()).append(",").append(c.getBlue()).append(")"); + if (e.hasMoreElements()) { + sb.append(";"); + } + } + return sb.toString(); + } + + public void setFlagString(String flagString) { + clear(); + StringTokenizer st = new StringTokenizer(flagString, ";"); + while (st.hasMoreTokens()) { + String s = st.nextToken(); + String flag = s.split("\\(")[0]; + Color color = FlagsSetting.toColor(s); + addElement(new FlagListItem(flag, color)); + } + } + + } + + class FlagListItem { + + Color color; + String flagString; + + public FlagListItem(String flagString, Color color) { + this.flagString = flagString; + this.color = color; + } + + public Color getColor() { + return color; + } + + public String getFlagString() { + return flagString; + } + + public void setColor(Color c) { + color = c; + } + + @Override + public String toString() { + return flagString; + } + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/FlagsSetting.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/FlagsSetting.java new file mode 100644 index 000000000000..e2f0cdf204fe --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/FlagsSetting.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.preferences; + +import java.awt.Color; +import java.io.Serializable; +import java.util.Hashtable; +import java.util.List; + +/** + * + * @author Thomas Wuerthinger + */ +public class FlagsSetting implements Serializable { + + private Hashtable flag2color; + private Hashtable priority; + private String flagString; + + + public FlagsSetting(String flagString) { + this.flagString = flagString; + flag2color = new Hashtable(); + priority = new Hashtable(); + String[] flags = flagString.split(";"); + + int z = 0; + for(String s : flags) { + String flag = s.split("\\(")[0]; + Color c = toColor(s); + flag2color.put(flag, c); + priority.put(flag, z); + z++; + } + } + + public Color getColor(List strings) { + int minPriority = Integer.MAX_VALUE; + Color result = null; + + for(String s : strings) { + Color curColor = flag2color.get(s); + if(curColor != null) { + int curPriority = priority.get(s); + if(curPriority < minPriority) { + minPriority = curPriority; + result = curColor; + } + } + } + + return result; + } + + public static Color toColor(String s) { + String sArr[] = s.split("\\("); + String Color = sArr[1].substring(0, sArr[1].length() - 1); + String ColorArr[] = Color.split(","); + int r = Integer.parseInt(ColorArr[0]); + int g = Integer.parseInt(ColorArr[1]); + int b = Integer.parseInt(ColorArr[2]); + return new Color(r, g, b); + } + + public String getFlagString() { + return flagString; + } + + @Override + public boolean equals(Object o) { + if(o==null) + return false; + return this.toString().equals(o.toString()); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 19 * hash + (this.flagString != null ? this.flagString.hashCode() : 0); + return hash; + } + + @Override + public String toString(){ + return "FlagSetting[" + flagString + "]"; + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/FontChooserDialog.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/FontChooserDialog.java new file mode 100644 index 000000000000..51c5f17c76bb --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/preferences/FontChooserDialog.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.preferences; + +import java.awt.Font; +import java.beans.PropertyEditor; +import java.beans.PropertyEditorManager; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; + +/** + * + * @author Bernhard Stiftner + */ +public class FontChooserDialog { + + /* + * Displays a font selection dialog. + * @return The selected font, or the initial font if the user chose + * to bail out + */ + public static Font show(Font initialFont) { + PropertyEditor pe = PropertyEditorManager.findEditor(Font.class); + if (pe == null) { + throw new RuntimeException("Could not find font editor component."); + } + pe.setValue(initialFont); + DialogDescriptor dd = new DialogDescriptor( + pe.getCustomEditor(), + "Choose Font"); + DialogDisplayer.getDefault().createDialog(dd).setVisible(true); + if (dd.getValue() == DialogDescriptor.OK_OPTION) { + Font f = (Font)pe.getValue(); + return f; + } + return initialFont; + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/BezierWidget.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/BezierWidget.java new file mode 100644 index 000000000000..1d7d4853cd8d --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/BezierWidget.java @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.visual; + +import org.netbeans.api.visual.widget.ConnectionWidget; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.CubicCurve2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.Point2D; +import java.util.List; +import org.netbeans.api.visual.anchor.AnchorShape; +import org.netbeans.api.visual.graph.GraphScene; +import org.netbeans.api.visual.widget.Scene; +import org.netbeans.api.visual.widget.Widget; + + +/** + * In comparison to the default ConnectionWidget this class is able to connect + * Widgets with a curve instead of a straight line sequence. Between two control + * points a curve is painted as cubic bezier curve the control points are + * calculated automaticaly they depend on the position of the prior- and the + * following control points. + * In conjunction with a suitable router the connection will be a straight line + * or a curve depending on the amount and the position of the controlpoints. + * Controlpoints supplied by the router, are treated as curve intersection points. + * For Reflexive edges the router doesn`t need to supply controlpoints, they + * get painted by this class automatically. If the router supplys more as 2 + * control points for a recursive edge the edge gets painted with the default + * curve approximation algorithm. + */ +public class BezierWidget extends ConnectionWidget { + private static final double BEZIER_SCALE = 0.3; + private static final double ENDPOINT_DEVIATION = 3;//curve endpoint approximation accuracy + + private GraphScene scene=null; + + public BezierWidget(Scene scene) { + super(scene); + } + + public BezierWidget(GraphScene scene) { + super(scene); + this.scene=scene; + } + + + private boolean isReflexive(){ + return getSourceAnchor().getRelatedWidget() == getTargetAnchor().getRelatedWidget(); + } + + + @Override + protected Rectangle calculateClientArea() { + Rectangle bounds = null; + if(this.getControlPoints().size()>0){ + for(Point p : this.getControlPoints()){ + if(bounds==null) + bounds = new Rectangle(p); + else + bounds.add(p); + } + bounds.grow(5,5); + } + if(isReflexive()){ + Widget related = this.getTargetAnchor().getRelatedWidget(); + bounds = related.convertLocalToScene(related.getBounds()); + bounds.grow(10, 10); + } + if(bounds==null) + bounds = super.calculateClientArea(); + + return bounds; + } + + + + //returns prefered location for an edge -1 for left and 1 for right + private int edgeBalance(Widget nodeWidget) { + if(scene == null) + return 1; + + Point nodeLocation = nodeWidget.getLocation(); + int left = 0, right = 0; + + Object node = scene.findObject(nodeWidget); + + for(Object e : scene.findNodeEdges(node, true, true)) {//inputedges + ConnectionWidget cw = (ConnectionWidget) scene.findWidget(e); + + if(cw != this) { + Widget targetNodeWidget = cw.getTargetAnchor().getRelatedWidget(); + + Point location; + if(targetNodeWidget == nodeWidget) { + Widget sourceNodeWidget = cw.getSourceAnchor().getRelatedWidget(); + location = sourceNodeWidget.getLocation(); + } else { + location = targetNodeWidget.getLocation(); + } + + if(location.x < nodeLocation.x) + left++; + else + right++; + } + } + if(left < right) + return -1; + else + return 1; + } + + + + + /** + * if the edge is reflexive its painted as a cyclic edge + * if there are 2 controlpoints the connection is painted as a straight line from the source to the targetanchor + * if there are more as 2 controlpoints the connection path between 2 control points is painted as bezier curve + */ + + @Override + protected void paintWidget () { + + List contrPoints = this.getControlPoints(); + int listSize = contrPoints.size(); + + Graphics2D gr = getGraphics (); + + if (listSize <= 2) { + if(isReflexive()) { //special case for reflexive connection widgets + Widget related = this.getTargetAnchor().getRelatedWidget(); + int position = this.edgeBalance(related); + Rectangle bounds = related.convertLocalToScene(related.getBounds()); + gr.setColor (getLineColor()); + Point first = new Point(); + Point last = new Point(); + double centerX = bounds.getCenterX(); + first.x = (int) (centerX + bounds.width / 4); + first.y = bounds.y + bounds.height; + last.x = first.x; + last.y = bounds.y; + + gr.setStroke(this.getStroke()); + + double cutDistance = this.getTargetAnchorShape().getCutDistance(); + double anchorAngle = Math.PI/-3.0; + double cutX = Math.abs(Math.cos(anchorAngle)*cutDistance); + double cutY = Math.abs(Math.sin(anchorAngle)*cutDistance); + int ydiff=first.y-last.y; + int endy = -ydiff; + double height=bounds.getHeight(); + double cy = height/4.0; + double cx=bounds.getWidth()/5.0; + double dcx = cx*2; + GeneralPath gp = new GeneralPath(); + gp.moveTo(0, 0); + gp.quadTo(0, cy, cx, cy); + gp.quadTo(dcx, cy, dcx, -height/2.0); + gp.quadTo(dcx, endy - cy, cy, -(cy+ydiff)); + gp.quadTo(cutX*1.5, endy - cy, cutX, endy-cutY); + + AffineTransform af = new AffineTransform(); + AnchorShape anchorShape = this.getTargetAnchorShape(); + + if(position < 0) { + first.x = (int) (centerX - bounds.width / 4); + af.translate(first.x, first.y); + af.scale(-1.0, 1.0); + last.x = first.x; + } else { + af.translate(first.x, first.y); + } + Shape s = gp.createTransformedShape(af); + gr.draw(s); + + if (last != null) { + AffineTransform previousTransform = gr.getTransform (); + gr.translate (last.x, last.y); + + if(position < 0) + gr.rotate(Math.PI - anchorAngle); + else + gr.rotate (anchorAngle); + + anchorShape.paint (gr, false); + gr.setTransform (previousTransform); + } + + } else { + super.paintWidget(); + } + return; + } + + //bezier curve... + GeneralPath curvePath = new GeneralPath(); + Point lastControlPoint = null; + double lastControlPointRotation = 0.0; + + Point prev = null; + for (int i = 0; i < listSize - 1; i++) { + Point cur = contrPoints.get(i); + Point next = contrPoints.get(i + 1); + Point nextnext = null; + if (i < listSize - 2) { + nextnext = contrPoints.get(i + 2); + } + + double len = cur.distance(next); + double scale = len * BEZIER_SCALE; + Point bezierFrom = null;//first ControlPoint + Point bezierTo = null;//second ControlPoint + + if (prev == null) { + //first point + curvePath.moveTo(cur.x, cur.y);//startpoint + bezierFrom = cur; + } else { + bezierFrom = new Point(next.x - prev.x, next.y - prev.y); + bezierFrom = scaleVector(bezierFrom, scale); + bezierFrom.translate(cur.x, cur.y); + } + + if (nextnext == null) {//next== last point (curve to) + lastControlPoint=next; + bezierTo = next;//set 2nd intermediate point to endpoint + GeneralPath lastseg = this.subdivide(cur, bezierFrom, bezierTo, next); + if(lastseg != null) + curvePath.append(lastseg, true); + break; + } else { + bezierTo = new Point(cur.x - nextnext.x, cur.y - nextnext.y); + bezierTo = scaleVector(bezierTo, scale); + bezierTo.translate(next.x, next.y); + } + + curvePath.curveTo( + bezierFrom.x, bezierFrom.y,//controlPoint1 + bezierTo.x, bezierTo.y,//controlPoint2 + next.x,next.y + ); + prev = cur; + } + Point2D cur = curvePath.getCurrentPoint(); + Point next = lastControlPoint; + + lastControlPointRotation = //anchor anchorAngle + Math.atan2 (cur.getY() - next.y, cur.getX() - next.x); + + Color previousColor = gr.getColor(); + gr.setColor (getLineColor()); + Stroke s = this.getStroke(); + gr.setStroke(s); + gr.setColor(this.getLineColor()); + gr.draw(curvePath); + + AffineTransform previousTransform = gr.getTransform (); + gr.translate (lastControlPoint.x, lastControlPoint.y); + gr.rotate (lastControlPointRotation); + AnchorShape targetAnchorShape = this.getTargetAnchorShape(); + targetAnchorShape.paint (gr, false); + gr.setTransform (previousTransform); + + //paint ControlPoints if enabled + if (isPaintControlPoints()) { + int last = listSize - 1; + for (int index = 0; index <= last; index ++) { + Point point = contrPoints.get (index); + previousTransform = gr.getTransform (); + gr.translate (point.x, point.y); + if (index == 0 || index == last) + getEndPointShape().paint (gr); + else + getControlPointShape().paint (gr); + gr.setTransform (previousTransform); + } + + } + gr.setColor(previousColor); + } + + + + private GeneralPath subdivide (Point b0, Point b1, Point b2, Point b3) { + double cutDistance = getTargetAnchorShape().getCutDistance(); + double minDistance = cutDistance - ENDPOINT_DEVIATION; + /** + * if the cutDistance is valid the last segment of the curve + * gets reduced by subdivision until the distance of the endpoint(epDistance) + * satisfys the condition (cutDistance > epDistance > (cutDistance - ENDPOINT-DEVIATION) + */ + if(cutDistance > minDistance && minDistance > 0 ) { + GeneralPath path = new GeneralPath(); + + path.moveTo(b0.x, b0.y); + + CubicCurve2D.Double left = new CubicCurve2D.Double( + b0.x, b0.y, + b1.x, b1.y, + b2.x, b2.y, + b3.x, b3.y); + + CubicCurve2D right=new CubicCurve2D.Double(); + left.subdivide(left, right); + double distance = b3.distance(left.getP2()); + //if the distance is bigger as the cutDistance the left segment is added + //and the right segment is divided again + while(distance>cutDistance){ + path.append(left, true); + right.subdivide(left, right); + distance = b3.distance(left.getP2()); + //if the devision removed to much the left segment is divided + while(distance < minDistance) { + //changes the distance to ~ (distance+distance/2) + left.subdivide(left, right); + distance = b3.distance(left.getP2()); + } + } + //append the last segment with (minDistance < distance < cutDistance) + //actually we should check if the a division happend, but this is very unlikly + path.append(left, true); + return path; + } + return null; + } + + + + + + private static Point scaleVector(Point vector, double len) { + double scale = Math.sqrt(vector.x * vector.x + vector.y * vector.y); + if(scale==0.0) return vector; + scale = len / scale; + return new Point( + Math.round(vector.x * (float)scale), + Math.round(vector.y * (float)scale)); + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/PolylineRouter.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/PolylineRouter.java new file mode 100644 index 000000000000..5a24494a5116 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/PolylineRouter.java @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.visual; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.geom.Line2D; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.netbeans.api.visual.anchor.Anchor; +import org.netbeans.api.visual.router.Router; +import org.netbeans.api.visual.widget.ConnectionWidget; +import org.netbeans.api.visual.widget.Widget; + +/** + * Router Class with Collision Detection + * + * + * The returned path is a straight line there is no node widget between the source and the target node, + * if there are nodewidgets in between it tries to find a path around those abstacles to avoid collisions. + * The algorithm for the search of this path is limited by a fixed number of iterations defined in + * NUMER_OF_ITERATIONS to return a result in reasonable time. If the calculation exceeds this limit the + * path returned contains at least 1 collision with a nodewidget. + */ + +public class PolylineRouter implements Router { + private static final float FACTOR = 0.70f; + + private static final int HEXPAND = 3; + private static final int VEXPAND = 3; + private static final int NUMBER_OF_ITERATIONS = 8; + + WidgetCollisionCollector collector; + + + public PolylineRouter(WidgetCollisionCollector collector){ + this.collector=collector; + } + + + public List routeConnection(final ConnectionWidget widget) { + if(!widget.isVisible()) return Collections.emptyList(); + + Anchor sourceAnchor = widget.getSourceAnchor(); + Anchor targetAnchor = widget.getTargetAnchor(); + + if(sourceAnchor == null || targetAnchor == null) + return null; + + Point start = sourceAnchor.compute(widget.getSourceAnchorEntry()).getAnchorSceneLocation(); + Point end = targetAnchor.compute(widget.getTargetAnchorEntry()).getAnchorSceneLocation(); + + Widget sourceWidget = sourceAnchor.getRelatedWidget(); + Widget targetWidget = targetAnchor.getRelatedWidget(); + + + if(sourceWidget == targetWidget){//reflexive edges doesnt need any path + return Collections.emptyList(); + } + + List nodeWidgets = new ArrayList(); + + if(collector != null){ + collector.collectCollisions(nodeWidgets); + //source and target widget are not treatet as obstacle + nodeWidgets.remove(sourceWidget); + nodeWidgets.remove(targetWidget); + } + + List controlPoints = optimize(nodeWidgets, widget, start, end); + + //size==2 => straight line + //size==3 => ensures a collision between cp0 and cp2 therefore its not possible to simplify it + if(controlPoints.size() > 3) + controlPoints = simplify(nodeWidgets, controlPoints); + + return controlPoints; + } + + + private List simplify(Collection nodeWidgets, List list) { + List result = new ArrayList(); + result.add( list.get(0) );//add startpoint + for (int i = 1; i < list.size(); i++) { + Point prev = list.get(i - 1); + for (int j = i; j < list.size(); j++) { + Point cur = list.get(j); + if (!intersects(nodeWidgets, prev, cur)) { + i = j; + } + } + result.add(list.get(i)); + } + return result; + } + + /** + * Computates the Anchorposition like the Rectangular anchor for a + * given widget as source/target and a controlpoint as opposit anchorposition + */ + + private Point computateAnchorPosition(Widget relatedWidget, Point controlPoint) { + Rectangle bounds = relatedWidget.getBounds(); + Point relatedLocation = relatedWidget.getLocation();//center of the widget + + if (bounds.isEmpty () || relatedLocation.equals (controlPoint)) + return relatedLocation; + + float dx = controlPoint.x - relatedLocation.x; + float dy = controlPoint.y - relatedLocation.y; + + float ddx = Math.abs (dx) / (float) bounds.width; + float ddy = Math.abs (dy) / (float) bounds.height; + + float scale = 0.5f / Math.max (ddx, ddy); + + Point point = new Point (Math.round (relatedLocation.x + scale * dx), + Math.round (relatedLocation.y + scale * dy)); + return point; + } + + + + private List optimize(Collection nodeWidgets, ConnectionWidget connWidget, Point start, Point end) { + + List list = new ArrayList(); + list.add(start); + list.add(end); + + boolean progress = true; + + for (int j = 0; progress && j < NUMBER_OF_ITERATIONS ; j++) { + progress = false; + List newList = new ArrayList(); + for (int i = 0; i < list.size() - 1 ; i++) { + Point cur = list.get(i); + Point next = list.get(i + 1); + newList.add(cur); + List intermediate = optimizeLine(nodeWidgets, cur, next); + if (intermediate != null && intermediate.size() > 0) { + progress = true; + newList.addAll(intermediate);//insert new controlpoints between cur and next + } + } + newList.add(list.get(list.size()-1));//add endpoint of the polyline + list = newList; + + } + + if(list.size() > 2) { + Widget sourceNode = connWidget.getSourceAnchor().getRelatedWidget(); + Widget targetNode = connWidget.getTargetAnchor().getRelatedWidget(); + Rectangle sourceBounds = sourceNode.convertLocalToScene(sourceNode.getBounds()); + Rectangle targetBounds = targetNode.convertLocalToScene(targetNode.getBounds()); + sourceBounds.grow(HEXPAND, VEXPAND); + + /** + * add only points which are not intersecting the source and the target + * widget bounds caused by invalid bad anchor positions. The first + * and the last point is ignored cause the anchor is recalculated + * anyway. + */ + ArrayList tmp = new ArrayList(); + int listSize=list.size(); + tmp.add(list.get(0)); + int i=0; + while(++i < listSize-1) { + Point p = list.get(i); + if(!sourceBounds.contains(p) || !targetBounds.contains(p)) + tmp.add(p); + } + + tmp.add(list.get(i)); + if(tmp.size() < 3) + return tmp; + + list=tmp; + //calculate a proper anchor position using the second/penultimate controlpoint for start/end + start = this.computateAnchorPosition(connWidget.getSourceAnchor().getRelatedWidget(), list.get(1)); + end = this.computateAnchorPosition(connWidget.getTargetAnchor().getRelatedWidget(), list.get(list.size()-2)); + list.set(0,start); + list.set(list.size()-1, end); + } + return list; + } + + + /** + * trys to optimize a line from p1 to p2 to avoid collisions with node widgets + * returns null if the line doesn`t intersect with any nodewidget + * or a list with immediate points between p1 and p2 to avoid collisions + */ + private List optimizeLine(Collection nodeWidgets, Point p1, Point p2) { + Line2D line = new Line2D.Double(p1, p2); + + for(Widget w : nodeWidgets ) { + if( w.isVisible() ) { + Rectangle r = w.convertLocalToScene(w.getBounds()); + r.grow(HEXPAND, VEXPAND); + + if (!line.intersects(r)) continue; + + Point location = w.getLocation(); + int distx = (int) (r.width * FACTOR); + int disty = (int) (r.height * FACTOR); + + int minIntersects = Integer.MAX_VALUE; + int min = Integer.MAX_VALUE; + List minSol = null; + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + if (i != 0 || j != 0) { + Point cur = new Point(location.x + i * distx, location.y + j * disty); + List list1 = new ArrayList(); + list1.add(p1); + list1.add(cur); + list1.add(p2); + int crossProd = Math.abs(crossProduct(p1, cur, p2)); + if (!intersects(w, list1)) { + Line2D line1 = new Line2D.Float(p1, cur); + Line2D line2 = new Line2D.Float(p2, cur); + int curIntersects = this.countNodeIntersections(nodeWidgets, line1, line2); + + if (curIntersects < minIntersects + || (curIntersects == minIntersects && crossProd < min)) { + minIntersects = curIntersects; + min = crossProd; + minSol = new ArrayList(); + minSol.add(cur); + } + } + + if (i == 0 || j == 0) { + Point cur1, cur2; + if (i == 0) { + cur1 = new Point(location.x + distx/2, location.y + j * disty); + cur2 = new Point(location.x - distx/2, location.y + j * disty); + } else { // (j == 0) + cur1 = new Point(location.x + i * distx, location.y + disty/2); + cur2 = new Point(location.x + i * distx, location.y - disty/2); + } + + Point vec1 = new Point(p1.x - cur1.x, p1.y - cur1.y); + int offset1 = vec1.x * vec1.x + vec1.y * vec1.y; + + Point vec2 = new Point(p1.x - cur2.x, p1.y - cur2.y); + int offset2 = vec2.x * vec2.x + vec2.y * vec2.y; + + if (offset2 < offset1) { + Point tmp = cur1; + cur1 = cur2; + cur2 = tmp; + } + + List list2 = new ArrayList(); + list2.add(p1); + list2.add(cur1); + list2.add(cur2); + list2.add(p2); + + int cross1 = crossProduct(p1, cur1, cur2); + int cross2 = crossProduct(cur1, cur2, p2); + + if (cross1 > 0) { + cross1 = 1; + } else if (cross1 < 0) { + cross1 = -1; + } + + if (cross2 > 0) { + cross2 = 1; + } else if (cross2 < 0) { + cross2 = -1; + } + if ((cross1 == cross2 || cross1 == 0 || cross2 == 0) && !intersects(w, list2)) { + Line2D line1 = new Line2D.Float(p1, cur1); + Line2D line2 = new Line2D.Float(cur1, cur2); + Line2D line3 = new Line2D.Float(p2, cur2); + int curIntersects = this.countNodeIntersections(nodeWidgets, line1, line2, line3); + + // This is a bit better + crossProd--; + + if (curIntersects < minIntersects + || (curIntersects == minIntersects && crossProd < min)) { + minIntersects = curIntersects; + min = crossProd; + minSol = new ArrayList(); + minSol.add(cur1); + minSol.add(cur2); + } + } + } + } + } + } + if (minSol != null) { + return minSol; + } + } + } + return null; + } + + + + private int countNodeIntersections(Collection nodeWidgets, Line2D... lines){ + int count=0; + for(Widget nw : nodeWidgets){ + if(nw.isVisible()) { + Rectangle bounds = nw.convertLocalToScene(nw.getBounds()); + for( Line2D line : lines){ + if(line.intersects(bounds)) + count++; + } + } + } + return count; + } + + + private boolean intersects(Collection nodeWidgets, Point start, Point end) { + List pointlist = new ArrayList(); + pointlist.add(start); + pointlist.add(end); + + for(Widget w : nodeWidgets){ + if(w.isVisible() && intersects(w, pointlist)) { + return true; + } + } + return false; + } + + + private boolean intersects(Widget w, List list) { + Rectangle r = w.convertLocalToScene(w.getBounds()); + r.grow(HEXPAND, VEXPAND); + return intersects(list, r); + } + + + private boolean intersects(List list, Rectangle rect){ + for(int i=1; i < list.size(); i++) { + Point cur = list.get(i-1); + Point next = list.get(i); + if(rect.intersectsLine(cur.x, cur.y, next.x, next.y)) + return true; + } + return false; + } + + + private int crossProduct(Point p1, Point p2, Point p3) { + Point off1 = new Point(p1.x - p2.x, p1.y - p2.y); + Point off2 = new Point(p3.x - p2.x, p3.y - p2.y); + return (off1.x * off2.y - off1.y * off2.x); + } + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/PolylineRouterV2.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/PolylineRouterV2.java new file mode 100644 index 000000000000..ce3a509b94c7 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/PolylineRouterV2.java @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.visual; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.geom.Line2D; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.netbeans.api.visual.anchor.Anchor; +import org.netbeans.api.visual.router.Router; +import org.netbeans.api.visual.widget.ConnectionWidget; +import org.netbeans.api.visual.widget.Widget; + +/** + * Router Class with Collision Detection + * + * + * The returned path is a straight line there is no node widget between the source and the target node, + * if there are nodewidgets in between it tries to find a path around those abstacles to avoid collisions. + * The algorithm for the search of this path is limited by a fixed number of iterations defined in + * NUMER_OF_ITERATIONS to return a result in reasonable time. If the calculation exceeds this limit the + * path returned contains at least 1 collision with a nodewidget. + * + * This class intend to solve the same problem as the class PolyLineRouter but + * uses slightly different methods. + * + uses another algorithm for solving the collision. + * + the obstacle bounds are calulated in advance to avoid a recalculation. + * - there is no heuristic, the shortest path around the widget is choosen asap. + * + * + * + */ + +public class PolylineRouterV2 implements Router { + private static final int HEXPAND = 3; + private static final int VEXPAND = 3; + private static final int NUMBER_OF_ITERATIONS = 8; + + WidgetCollisionCollector collector; + + + public PolylineRouterV2(WidgetCollisionCollector collector){ + this.collector=collector; + } + + + public List routeConnection(final ConnectionWidget widget) { + if(!widget.isVisible()) return Collections.emptyList(); + + Anchor sourceAnchor = widget.getSourceAnchor(); + Anchor targetAnchor = widget.getTargetAnchor(); + + if(sourceAnchor == null || targetAnchor == null) + return null; + + Point start = sourceAnchor.compute(widget.getSourceAnchorEntry()).getAnchorSceneLocation(); + Point end = targetAnchor.compute(widget.getTargetAnchorEntry()).getAnchorSceneLocation(); + + Widget sourceWidget = sourceAnchor.getRelatedWidget(); + Widget targetWidget = targetAnchor.getRelatedWidget(); + + + if(sourceWidget == targetWidget){//reflexive edges doesnt need any path + return Collections.emptyList(); + } + + + Point srcCenter = this.getSceneLocation(sourceWidget); + Point tarCenter = this.getSceneLocation(targetWidget); + + List widgetObstacles = new ArrayList(); + + if(collector != null){ + collector.collectCollisions(widgetObstacles); + } + + List obstacles = new ArrayList(widgetObstacles.size()); + this.collectObstacles(obstacles, widgetObstacles, widget); + + + List controlPoints = optimize(obstacles, srcCenter, tarCenter); +// size==2 => straight line +// size==3 => ensures a collision between cp0 and cp2 therefore its not possible to simplify it + if(controlPoints.size() > 3){ + Point rstart = this.computateAnchorPosition(sourceWidget, controlPoints.get(1)); + Point rend = this.computateAnchorPosition(targetWidget, controlPoints.get(controlPoints.size()-2)); + controlPoints.set(0, rstart); + controlPoints.set(controlPoints.size()-1, rend); + controlPoints = simplify(obstacles, controlPoints); + } else if (controlPoints.size()>=2){ + //use old points + controlPoints.set(0, start); + controlPoints.set(controlPoints.size()-1, end); + + } + return controlPoints; + } + + + private int collectObstacles(List colrects, List colwidgets , ConnectionWidget cw){ + int count=0; + Anchor sourceAnchor = cw.getSourceAnchor(); + Anchor targetAnchor = cw.getTargetAnchor(); + Widget sourceWidget = sourceAnchor.getRelatedWidget(); + Widget targetWidget = targetAnchor.getRelatedWidget(); + Point start = sourceAnchor.compute(cw.getSourceAnchorEntry()).getAnchorSceneLocation(); + Point end = targetAnchor.compute(cw.getTargetAnchorEntry()).getAnchorSceneLocation(); + + for(Widget w : colwidgets){ + + if(w==sourceWidget || w == targetWidget) continue; + + Rectangle r = w.convertLocalToScene(w.getBounds()); + r.grow(HEXPAND, VEXPAND); + if(r.intersectsLine(start.x,start.y,end.x,end.y)) + count++; + colrects.add(r); + } + return count; + } + + + private Point center (Rectangle bounds) { + return new Point (bounds.x + bounds.width / 2, bounds.y + bounds.height / 2); + } + + /** + * Returns the scene location of a related widget. + * bounds might be null if the widget was not added to the scene + * @return the scene location; null if no related widget is assigned + */ + public Point getSceneLocation (Widget relatedWidget) { + if (relatedWidget != null) { + Rectangle bounds = relatedWidget.getBounds (); + if(bounds != null) + return center(relatedWidget.convertLocalToScene(bounds)); + } + return null; + } + + /** + * Computates the Anchorposition like the Rectangular anchor for a + * given widget as source/target and a controlpoint as opposit anchorposition + */ + + private Point computateAnchorPosition(Widget relatedWidget, Point controlPoint) { + Rectangle bounds = relatedWidget.getBounds(); + + //todo: fix, center of widget must be cacluated trough the bounds + //since there are some wheird widgets where the location is not the center of the widget + Point relatedLocation = relatedWidget.getLocation();//center of the widget + + if (bounds.isEmpty () || relatedLocation.equals (controlPoint)) + return relatedLocation; + + float dx = controlPoint.x - relatedLocation.x; + float dy = controlPoint.y - relatedLocation.y; + + float ddx = Math.abs (dx) / (float) bounds.width; + float ddy = Math.abs (dy) / (float) bounds.height; + + float scale = 0.5f / Math.max (ddx, ddy); + + Point point = new Point (Math.round (relatedLocation.x + scale * dx), + Math.round (relatedLocation.y + scale * dy)); + return point; + } + + private List simplify(List obstacles, List list) { + List result = new ArrayList(list.size()); + result.add( list.get(0) );//add startpoint + for (int i = 1; i < list.size(); i++) { + Point prev = list.get(i - 1); + for (int j = i; j < list.size(); j++) { + Point cur = list.get(j); + if (!intersects(obstacles, prev, cur)) { + i = j; + } + } + result.add(list.get(i)); + } + return result; + } + + private List optimize(List nodeWidgets, Point start, Point end) { + + List list = new ArrayList(); + list.add(start); + list.add(end); + + boolean progress = true; + + for (int j = 0; progress && j < NUMBER_OF_ITERATIONS ; j++) { + progress = false; + List newList = new ArrayList(); + for (int i = 0; i < list.size() - 1 ; i++) { + Point cur = list.get(i); + Point next = list.get(i + 1); + newList.add(cur); + List intermediate = optimizeLine(nodeWidgets, cur, next); + if (intermediate != null && intermediate.size() > 0) { + progress = true; + newList.addAll(intermediate);//insert new controlpoints between cur and next + } + } + newList.add(list.get(list.size()-1));//add endpoint of the polyline + list = newList; + + } + + return list; + } + + + /** + * trys to optimize a line from p1 to p2 to avoid collisions with rectangles + * returns null if the line doesn`t intersect with any nodewidget or + * if the obstacles are overlapping + * ---------------------------------------------------------------------- + * if the collision is solved it returns a list with immediate points + * between p1 and p2. The points are taken from hull points of and grown + * rectangle. + */ + private List optimizeLine(List obstacles, Point p1, Point p2) { + Line2D line = new Line2D.Double(p1, p2); + boolean inbounds=false; + Rectangle ibr=null; + ArrayList sol = new ArrayList(); + boolean leftIntersection; + boolean rightIntersection; + boolean bottomIntersection; + boolean topIntersection; + Point interLeft=new Point(); + Point interRight=new Point(); + Point interBot=new Point(); + Point interTop=new Point(); + + + + for(Rectangle r : obstacles ) { + if (!line.intersects(r)) continue; + + int w=r.width+2; + int h=r.height+2; + Point topLeft = r.getLocation(); + topLeft.x-=1; + topLeft.y-=1; + Point topRight = new Point(topLeft.x+w, topLeft.y); + Point bottomLeft = new Point(topLeft.x, topLeft.y+h); + Point bottomRight = new Point(topRight.x, bottomLeft.y); + leftIntersection = findIntersectionPoint(p1, p2, topLeft, bottomLeft, interLeft); + rightIntersection = findIntersectionPoint(p1, p2, topRight, bottomRight, interRight); + bottomIntersection = findIntersectionPoint(p1, p2, bottomLeft, bottomRight, interBot); + topIntersection = findIntersectionPoint(p1, p2, topLeft, topRight, interTop); + + //Intersection points are not used yet. This could be actually a + //good approach to avoid additional collisions because it would be + //still the same vector. + + if(leftIntersection) { + if(topIntersection) {//left and top + sol.add(topLeft); + } + else if(bottomIntersection){//left and bottom + sol.add(bottomLeft); + } + else if(rightIntersection){//left and right + double disttl = topLeft.distance(p1); + double distbl = bottomLeft.distance(p1); + if(disttl > distbl){ + //pass at the bottom + double distbr = bottomRight.distance(p1); + if(distbl < distbr){ + //from the left to the right + sol.add(bottomLeft); + sol.add(bottomRight); + } else { + //from the right to the left + sol.add(bottomRight); + sol.add(bottomLeft); + } + } else { + //pass at the top + double disttr = topRight.distance(p1); + if(disttl < disttr){ + //from the left to the right + sol.add(topLeft); + sol.add(topRight); + } else { + //from the right to the left + sol.add(topRight); + sol.add(topLeft); + } + } + } else {//only left => inside bounds + inbounds=true; + } + } else if (rightIntersection) { + if(topIntersection) {//right and top + sol.add(topRight); + } + else if(bottomIntersection){//right and bottom + sol.add(bottomRight); + } else { //only right => inside the bounds + inbounds=true; + } + } else if (topIntersection && bottomIntersection) {//top and bottom + double disttop = interTop.distance(p1); + double distbot = interBot.distance(p1); + if(disttop < distbot ){ + //from the top to the bottom + double distleft = interTop.distance(topLeft); + double distright = interTop.distance(topRight); + if(distleft < distright){ + //pass left + sol.add(topLeft); + sol.add(bottomLeft); + } else { + //pass right + sol.add(topRight); + sol.add(bottomRight); + } + } else { + //from the bottom to the top + double distleft = interBot.distance(bottomLeft); + double distright = interBot.distance(bottomRight); + if(distleft < distright){ + //pass left + sol.add(bottomLeft); + sol.add(topLeft); + } else { + //pass right + sol.add(bottomRight); + sol.add(topRight); + } + } + } else {//only bottom or only top + inbounds=true; + } /* ENDIF */ + + //breakpoint <-- collision detected + + if(sol.size()>0) {//solution found + assert(!inbounds); + return sol; + } else { //no solution found=> inbounds + assert(inbounds); + assert(sol.size()==0); + //handle collision or just skip it and search for the next collisionj + ibr=r; + //jump out of the loop to able to interate over the obstacles + break; + } + }/* end foreach obstacle */ + + if(inbounds || ibr != null){ + assert(inbounds); + assert(ibr!=null); + } + return null;//no collison found + }/* end optimizeLine */ + + + + //check intersection between line p0->p1 for a given set of obstacles + private static boolean intersects(List obstacles, Point p0, Point p1) { + for(Rectangle r : obstacles){ + if(r.intersectsLine(p0.x, p0.y, p1.x, p1.y)) + return true; + } + return false; + } + + + private boolean findIntersectionPoint( + Point p0, Point p1, Point p2, Point p3, Point pI) { + float q = (p0.y - p2.y)*(p3.x - p2.x) - (p0.x - p2.x)*(p3.y - p2.y); + float d = (p1.x - p0.x)*(p3.y - p2.y) - (p1.y - p0.y)*(p3.x - p2.x); + + //parallel ? + if(d==0) return false; + + float r = q / d; + q = (p0.y - p2.y)*(p1.x - p0.x) - (p0.x - p2.x)*(p1.y - p0.y); + + float s = q / d; + if(r<0 || r>1 || s<0 || s>1) return false; + + pI.x = p0.x + (int) (0.5f + r * (p1.x - p0.x)); + pI.y = p0.y + (int) (0.5f + r * (p1.y - p0.y)); + return true; + } +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/SplineConnectionWidget.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/SplineConnectionWidget.java new file mode 100644 index 000000000000..e3552cb10b1a --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/SplineConnectionWidget.java @@ -0,0 +1,563 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.visual; + +import org.netbeans.api.visual.widget.ConnectionWidget; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.CubicCurve2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.List; +import org.netbeans.api.visual.anchor.AnchorShape; +import org.netbeans.api.visual.graph.GraphScene; +import org.netbeans.api.visual.widget.Scene; +import org.netbeans.api.visual.widget.Widget; + + +/** + * In comparison to the default ConnectionWidget this class is able to connect + * widgets with a curve instead of a straight line sequence. + * In conjunction with a suitable router the connection will be a straight line + * or a curve depending on the amount and the position of the controlpoints. + * Controlpoints supplied by the router, are treated as curve intersection points. + * For Reflexive edges the router doesn`t necessarily need to supply + * any controlpoints, they get painted by this automatically, this can be + * excepted if the router supplys more as 2 control points for a self edge, + * then the edge gets painted with the default curve interpolation algorithm. + * The method used for drawing curves uses a piecewise cubic interpolation + * algorithm. Between two control points a curve is painted as cubic bezier + * curve the, inner bezier points are calculated automatically with FMILL + * tangents and a chord parametrization, this interpolant is also known as + * cutmull-rom spline. The resulting spline fullfills c^1 continuity. + * The the end points the interpolation algorithm uses the bessel end condition. + */ + +public class SplineConnectionWidget extends ConnectionWidget { + private static final double ENDPOINT_DEVIATION = 3;//curve endpoint approximation accuracy + private static final double HIT_DISTANCE_SQUARE = 4.0;//distance for intersection test + private GraphScene scene=null; + private Point2D [] bezierPoints = null; + + public SplineConnectionWidget(Scene scene) { + super(scene); + if(scene instanceof GraphScene) + this.scene=(GraphScene) scene; + } + + //check for self - edge + private boolean isReflexive(){ + return getSourceAnchor().getRelatedWidget() == getTargetAnchor().getRelatedWidget(); + } + + + @Override + protected Rectangle calculateClientArea() { + + Rectangle bounds = null; + + if(this.getControlPoints().size()>2){ + bezierPoints = createBezierPoints(getControlPoints()); + } + //minmax method - returns the smallest bounding rectangle + //Curves and surfaces for CAGD (3rd ed.), p.54 + //exact calculation of the bounding min rect + if(bezierPoints != null) { + Rectangle2D bounds2D = null; + for (int i = 0; i < bezierPoints.length; i++) { + Point2D point = bezierPoints[i]; + if(bounds2D==null) + bounds2D = new Rectangle2D.Double(point.getX(),point.getY(),0,0); + else + bounds2D.add(point); + } + bounds = bounds2D.getBounds(); + bounds.grow(2, 2); + + } else if (getControlPoints().size()>0){ + for(Point p : this.getControlPoints()){ + if(bounds==null) + bounds = new Rectangle(p); + else + bounds.add(p); + } + bounds.grow(5,5); + + } else if (isReflexive()) { + Widget related = this.getTargetAnchor().getRelatedWidget(); + bounds = related.convertLocalToScene(related.getBounds()); + bounds.grow(10, 10); + } + + if(bounds==null) + bounds = super.calculateClientArea(); + + return bounds; + } + + + /** + * if the edge is reflexive its painted as a cyclic self edge, if there + * are two controlpoints the connection is painted as a straight line from + * the source to the targetanchor, if there are more as 2 controlpoints the + * connection path between two control points is painted as cutmull rom + * spline with bessel end tangents. + */ + @Override + protected void paintWidget () { + List contrPoints = this.getControlPoints(); + int listSize = contrPoints.size(); + + Graphics2D gr = getGraphics (); + + if (listSize <= 2) { + this.bezierPoints=null;//set bezier Points null for calulateClientArea() + if(isReflexive()) { //special case for reflexive connection widgets + this.drawReflexive(gr); + } else { + super.paintWidget(); + } + return; + } + + //bezier curve... listSize > 2 + GeneralPath curvePath = new GeneralPath(); + double lastControlPointRotation = 0.0; + + + Point2D [] bezPoints = this.createBezierPoints(contrPoints); + curvePath.moveTo(bezPoints[0].getX(), bezPoints[0].getY());//b00 + + //last segment is added by subdivision thats why its -5 + for (int i = 1; i < bezPoints.length-5; i+=3) { + curvePath.curveTo( + bezPoints[i].getX(), bezPoints[i].getY(),//b1i + bezPoints[i+1].getX(), bezPoints[i+1].getY(),//b2i + bezPoints[i+2].getX(), bezPoints[i+2].getY());//b3i + + } + + GeneralPath lastseg = subdivide2D( + bezPoints[bezPoints.length-4], + bezPoints[bezPoints.length-3], + bezPoints[bezPoints.length-2], + bezPoints[bezPoints.length-1]); + + + if(lastseg != null) + curvePath.append(lastseg, true); + + Point2D cur = curvePath.getCurrentPoint(); + Point lastControlPoint = contrPoints.get(listSize-1); + + lastControlPointRotation = //anchor anchorAngle + Math.atan2 (cur.getY() - lastControlPoint.y, cur.getX() - lastControlPoint.x); + + gr.setStroke(getStroke()); + gr.setColor(getLineColor()); + gr.draw(curvePath); + + + AffineTransform previousTransform = gr.getTransform (); + gr.translate (lastControlPoint.x, lastControlPoint.y); + gr.rotate (lastControlPointRotation); + AnchorShape targetAnchorShape = this.getTargetAnchorShape(); + targetAnchorShape.paint (gr, false); + gr.setTransform (previousTransform); + + //paint ControlPoints if enabled + if (isPaintControlPoints()) { + int last = listSize - 1; + for (int index = 0; index <= last; index ++) { + Point point = contrPoints.get (index); + previousTransform = gr.getTransform (); + gr.translate (point.x, point.y); + if (index == 0 || index == last) + getEndPointShape().paint (gr); + else + getControlPointShape().paint (gr); + gr.setTransform (previousTransform); + } + + } + } + + private void drawReflexive(Graphics2D gr){ + Widget related = this.getTargetAnchor().getRelatedWidget(); + int position = this.edgeBalance(related); + Rectangle bounds = related.convertLocalToScene(related.getBounds()); + gr.setColor (getLineColor()); + Point first = new Point(); + Point last = new Point(); + double centerX = bounds.getCenterX(); + first.x = (int) (centerX + bounds.width / 4); + first.y = bounds.y + bounds.height; + last.x = first.x; + last.y = bounds.y; + + gr.setStroke(this.getStroke()); + + double cutDistance = this.getTargetAnchorShape().getCutDistance(); + double anchorAngle = Math.PI/-3.0; + double cutX = Math.abs(Math.cos(anchorAngle)*cutDistance); + double cutY = Math.abs(Math.sin(anchorAngle)*cutDistance); + int ydiff=first.y-last.y; + int endy = -ydiff; + double height=bounds.getHeight(); + double cy = height/4.0; + double cx=bounds.getWidth()/5.0; + double dcx = cx*2; + GeneralPath gp = new GeneralPath(); + gp.moveTo(0, 0); + gp.quadTo(0, cy, cx, cy); + gp.quadTo(dcx, cy, dcx, -height/2.0); + gp.quadTo(dcx, endy - cy, cy, -(cy+ydiff)); + gp.quadTo(cutX*1.5, endy - cy, cutX, endy-cutY); + + AffineTransform af = new AffineTransform(); + AnchorShape anchorShape = this.getTargetAnchorShape(); + + if(position < 0) { + first.x = (int) (centerX - bounds.width / 4); + af.translate(first.x, first.y); + af.scale(-1.0, 1.0); + last.x = first.x; + } else { + af.translate(first.x, first.y); + } + Shape s = gp.createTransformedShape(af); + gr.draw(s); + + if (last != null) { + AffineTransform previousTransform = gr.getTransform (); + gr.translate (last.x, last.y); + + if(position < 0) + gr.rotate(Math.PI - anchorAngle); + else + gr.rotate (anchorAngle); + + anchorShape.paint (gr, false); + gr.setTransform (previousTransform); + } + } + + //returns prefered location for an edge -1 for left and 1 for right + private int edgeBalance(Widget nodeWidget) { + if(scene == null) + return 1; + + Point nodeLocation = nodeWidget.getLocation(); + int left = 0, right = 0; + + Object node = scene.findObject(nodeWidget); + + for(Object e : scene.findNodeEdges(node, true, true)) {//inputedges + ConnectionWidget cw = (ConnectionWidget) scene.findWidget(e); + + if(cw != this) { + Widget targetNodeWidget = cw.getTargetAnchor().getRelatedWidget(); + + Point location; + if(targetNodeWidget == nodeWidget) { + Widget sourceNodeWidget = cw.getSourceAnchor().getRelatedWidget(); + location = sourceNodeWidget.getLocation(); + } else { + location = targetNodeWidget.getLocation(); + } + + if(location.x < nodeLocation.x) + left++; + else + right++; + } + } + if(left < right) + return -1; + else + return 1; + } + + + private Point2D[] createBezierPoints(List list){ + if(list.size()<3) return null ; + + + int lastIdx = list.size()-1; + + + //chord length parametrization + double[] uis = new double[list.size()]; + uis[0]=0; + uis[1] = list.get(1).distance(list.get(0)); + for (int i = 1; i < uis.length; i++) { + Point cur = list.get(i); + Point prev = list.get(i-1); + uis[i]=uis[i-1]+ cur.distance(prev); + } + + + for (int i = 1; i < uis.length; i++) { + uis[i] /= uis[lastIdx]; + + } + double[] delta = new double[uis.length-1]; + for (int i = 0; i < delta.length; i++) { + double ui = uis[i]; + double uin = uis[i+1]; + delta[i] = uin-ui; + } + + + //FMILL tangent directions (chord length) + Point2D[] tangents = new Point2D[list.size()]; + + for (int i = 1; i < list.size()-1; i++) { + Point xBefore = list.get(i-1); + Point xAfter = list.get(i+1); + Point2D.Double tangent = new Point2D.Double (xAfter.x - xBefore.x, xAfter.y - xBefore.y); + tangents[i] = tangent; + } + + + Point2D [] bezPoints = new Point2D[(list.size()-1)*2+list.size()]; + //Catmull-Rom + for (int i = 1; i < list.size()-1; i++) { + Point b3i = list.get(i); + Point2D b3ib = b3iBefore(b3i, delta[i-1], delta[i], tangents[i]); + Point2D b3ia = b3iAfter(b3i, delta[i-1], delta[i], tangents[i]); + bezPoints[3*i] = b3i; + bezPoints[3*i-1] = b3ib; + bezPoints[3*i+1] = b3ia; + } + bezPoints[0] = list.get(0); + bezPoints[bezPoints.length-1] = list.get(list.size()-1); + + Point p0 = list.get(0); + Point p1 = list.get(1); + Point p2 = list.get(2); + Point pL_2 = list.get(lastIdx-2); + Point pL_1 = list.get(lastIdx-1); + Point pL = list.get(lastIdx); + + Point2D m1 = besselTangent(delta[0], delta[1], p0, p1, p2); + Point2D m0 = besselEndTangent(p0, p1, delta[0], m1); + + Point2D mLb = besselTangent(delta[delta.length-2], delta[delta.length-1], + pL_2,pL_1, pL); + Point2D mL = besselEndTangent(pL_1, pL, delta[delta.length-1], mLb); + + Point2D scaleM0 = scale(normalize(m0), p0.distance(p1));//increase distx/distxl to make curve rounder at the end + Point2D scaleML = scale(normalize(mL), pL.distance(pL_1)); + //Catmull-Rom for bessel points + Point2D b30a = b3iAfter(p0, delta[0], delta[0],scaleM0); + Point2D b33b = b3iBefore(pL, delta[delta.length-1], delta[delta.length-1],scaleML); + + bezPoints[1] = b30a; + bezPoints[bezPoints.length-2] = b33b; + + return bezPoints; + } + + + + private static Point2D besselTangent(double delta_ib, double delta_i, Point2D p0, Point2D p1 , Point2D p2){ + double alpha_i = delta_ib/(delta_ib+delta_i); + + double x = (1-alpha_i)/delta_ib * (p1.getX() - p0.getX()) + + alpha_i/delta_i * (p2.getX()-p1.getX()); + double y = (1-alpha_i)/delta_ib * (p1.getY() - p0.getY()) + + alpha_i/delta_i * (p2.getY()-p1.getY()); + + return new Point2D.Double(x,y); + } + + private static Point2D besselEndTangent(Point2D p0, Point2D p1, double delta_u, Point2D m){ + double x = 2*((p1.getX()-p0.getX())/delta_u) - m.getX(); + double y = 2*((p1.getY()-p0.getY())/delta_u) - m.getY(); + return new Point2D.Double(x,y); + } + + private static Point2D b3iBefore(Point2D b3i, double delta_ib, double delta_i, Point2D li){ + double x = b3i.getX() - (delta_ib/(3*(delta_ib+delta_i)))*li.getX(); + double y = b3i.getY() - (delta_ib/(3*(delta_ib+delta_i)))*li.getY(); + return new Point.Double(x,y); + } + + private static Point2D b3iAfter(Point2D b3i, double delta_ib,double delta_i,Point2D li){ + double x = b3i.getX() + (delta_i/(3*(delta_ib+delta_i)))*li.getX(); + double y = b3i.getY() + (delta_i/(3*(delta_ib+delta_i)))*li.getY(); + return new Point.Double(x,y); + } + + + + + //returns length of vector v + private static double norm(Point2D v){ + return Math.sqrt(v.getX()*v.getX()+v.getY()*v.getY()); + } + + //returns unity vector of vector v + private static Point2D normalize(Point2D v){ + double norm = norm(v); + if(norm==0) return new Point2D.Double(v.getX(), v.getY()); + return new Point2D.Double(v.getX()/norm , v.getY()/norm); + } + + //scale vector to size of length + private static Point2D scale(Point2D v, double length){ + Point2D tmp = normalize(v); + return new Point2D.Double(tmp.getX()*length, tmp.getY()*length); + } + + + + private GeneralPath subdivide2D (Point2D b0, Point2D b1, Point2D b2, Point2D b3) { + //set 2nd intermediate point to endpoint + //we could actually use another "better" point if we like to have a smoother curve + + double cutDistance = getTargetAnchorShape().getCutDistance(); + double minDistance = cutDistance - ENDPOINT_DEVIATION; + /** + * if the cutDistance is valid the last segment of the curve + * gets reduced by subdivision until the distance of the endpoint(epDistance) + * satisfys the condition (cutDistance > epDistance > (cutDistance - ENDPOINT-DEVIATION) + */ + if(cutDistance > minDistance && minDistance > 0 ) { + GeneralPath path = new GeneralPath(); + + path.moveTo(b0.getX(), b0.getY()); + + CubicCurve2D.Double curve = new CubicCurve2D.Double( + b0.getX(), b0.getY(), + b1.getX(), b1.getY(), + b2.getX(), b2.getY(), + b3.getX(), b3.getY()); + + + + CubicCurve2D right=new CubicCurve2D.Double(); + CubicCurve2D left=new CubicCurve2D.Double(); + curve.subdivide(left, right); + double distance = b3.distance(left.getP2()); + //if the distance is bigger as the cutDistance the left segment is added + //and the right segment is divided again + while(distance>cutDistance){ + path.append(left, true); + right.subdivide(left, right); + distance = b3.distance(left.getP2()); + //if the devision removed to much the left segment is divided + while(distance < minDistance) { + //changes the distance to ~ (distance+distance/2) + left.subdivide(left, right); + distance = b3.distance(left.getP2()); + } + } + //append the last segment with (minDistance < distance < cutDistance) + path.append(left, true); + return path; + } + return null; + } + + + /** + * Returns whether a specified local point pL is a part of the connection + * widget. + * First it make a rough bounds check + * for Line Segments => use Super call (ConnectionWidget.isHitAt(pL)). + * for self-edges => its sufficent to return getBounds.contains(pL). + * for Splines => Interate over all Partitial segments of the curve and make + * a minmax check with the bezier points. If pL is inside the minmax + * rectangle of one segment the curve is constructed and subdivided until + * the distance d between center point pC (of the bounding rectangle) + * and pL is below HIT_DISTANCE_SQUARE, in this case it returns true. + * If no no minmax check was successful or the subdivision lead to an + * rectangle witch doesn`t contain pL return false. + * @param localLocation the local location + * @return true, if the location is a part of the connection widget + */ + @Override + public boolean isHitAt(Point localLocation) { + if(!isVisible() || !getBounds ().contains (localLocation)) + return false; + + List controlPoints = getControlPoints (); + if(controlPoints.size() <=2){ + if(isReflexive()) return true; + return super.isHitAt(localLocation); + } + + if(bezierPoints != null) { + for (int i = 0; i < bezierPoints.length-1; i+=3) { + Point2D b0 = bezierPoints[i]; + Point2D b1 = bezierPoints[i+1]; + Point2D b2 = bezierPoints[i+2]; + Point2D b3 = bezierPoints[i+3]; + + CubicCurve2D left = new CubicCurve2D.Double( + b0.getX(), b0.getY(), + b1.getX(), b1.getY(), + b2.getX(), b2.getY(), + b3.getX(), b3.getY()); + + + Rectangle2D bounds = left.getBounds2D(); + while(bounds.contains(localLocation)) { + //calculate the center and use HIT_DISTANCE_SQUARE for a range check + Point2D test = new Point2D.Double(bounds.getCenterX(),bounds.getCenterY()); + if(test.distance(localLocation) < HIT_DISTANCE_SQUARE){ + return true; + } + + + CubicCurve2D right = new CubicCurve2D.Double(); + left.subdivide(left, right); + Rectangle2D lb2d = left.getBounds2D(); + Rectangle2D rb2d = right.getBounds2D(); + if( lb2d.contains(localLocation)){ + bounds = lb2d; + } else if (rb2d.contains(localLocation)) { + left = right; + bounds = rb2d; + } else { + return false; + } + + }//end while + }//end for + } + return false; + } + + +} + + + + diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/WidgetCollisionCollector.java b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/WidgetCollisionCollector.java new file mode 100644 index 000000000000..860764d38f14 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/java/at/ssw/visualizer/cfg/visual/WidgetCollisionCollector.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.cfg.visual; + +import java.util.List; +import org.netbeans.api.visual.widget.Widget; + + +public interface WidgetCollisionCollector { + + //returns a list of widgets which should be handled as obstacles + void collectCollisions(List collisions); + +} diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/ControlFlowEditor/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..2b8164a66f4b --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.cfg +OpenIDE-Module-Layer: at/ssw/visualizer/cfg/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/cfg/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/Bundle.properties b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/Bundle.properties new file mode 100644 index 000000000000..8a928b7a69ad --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Control Flow Editor diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/arrangebfs.gif b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/arrangebfs.gif new file mode 100644 index 0000000000000000000000000000000000000000..2f093c74e7f0db1fdef52ab95881044b41c07343 GIT binary patch literal 338 zcmZ?wbhEHb6krfwxXQrrpMgQG!NamW$azw9;MAy)Y0+VmL@7>#a(fP!_kKFlkA0_R92}l^InlvI|!g6|O5NSyNQ9p)kL!rJ%gE zxT<~Ov9_Zp&Yin>^~&|zSFhi`cH_>Sdk^p4e{}D`X)}opmh}+k0m&Jl3}N!0`izPaZvS z?)16K45S0apDdgV42%pqAgv%jF>n+yFmv!IENp0N{o=P;zEnX|7&%L(^0_U=5>~gsr3igEas^C2Y+A literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/arrangeloop.gif b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/arrangeloop.gif new file mode 100644 index 0000000000000000000000000000000000000000..6c7d10410c77806c4c0615acd271563ba6f6a566 GIT binary patch literal 337 zcmZ?wbhEHb6krfwxXQqwU+*7VQ~aNSL9M~VvOUOoQgqxCMntOOmr!rsu58s9KR- zxUQgNZBfaF!orHy;;Qztny$+Fp2f>IEML84;jy;sH}5h~2`K(#;bdT7V$cDZ2l5jG zM;-$!hls;MhPFlyVXq$vi=10p1lTi9Y+z(K(!#}8U=Waa#I=Q&$wt6I>B#X$1-3~$ z5GN literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/autosize.gif b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/autosize.gif new file mode 100644 index 0000000000000000000000000000000000000000..073f47fb367fd24f290ef90878a13a033d7eeeb3 GIT binary patch literal 186 zcmZ?wbhEHb6krfwIKlt||NsAI6nY!mTCCRKkvOF!a%#S1dyvoMWamlIp|i6>XJ?1a z$cmnqA3QxhZb4z;anM@3$w;S}c9;WshHOkoXx5=TiY*Ash_W00^;5fB*mh literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/autosize_selection.gif b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/autosize_selection.gif new file mode 100644 index 0000000000000000000000000000000000000000..ae69de6d781690f9b220b4c9243458cc99c725aa GIT binary patch literal 194 zcmZ?wbhEHb6krfwIKlt||NsAI6nY!mTCCRKkvOF!a%#S1dyvoMWamlIp|i6>XJ?1a z$cmnqA3QxhZb4z;C-voBd42B1;hR~Tgw}|QO+LMJtp*E&H2{`rP|yGX literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/bezierrouter.gif b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/bezierrouter.gif new file mode 100644 index 0000000000000000000000000000000000000000..618d15c2c4fef6ba8223d57ea9af8528aa4409fb GIT binary patch literal 193 zcmZ?wbhEHb6krfwIKsg2pMgQG!NamW$azw9;MAy)Y0*BDlY^(Hht0?eoef0U(ev`- z78E8eNzPuGp0hHeYDIS8x`L7oh52PI3y-xi5CV!nSr|cTbU*~iP6k#xg$+DB3G-I0 z^az=IsQl)Ur; Lw)S%&91PX~I;A|a literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/cfg.gif b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/cfg.gif new file mode 100644 index 0000000000000000000000000000000000000000..695e5a5cfa5b259193c4caa06228d1d38282d05e GIT binary patch literal 209 zcmZ?wbhEHb6krfwIKsfNc=^WsvX-qo52`hIShffGOip&56dgJ{D|B{t*o>^`dHKQ9 z)8iHt22PC%nHHV2B)M>1LCJ=~?3L*`D>JHAWG_6{_W%EX2HZgLCkrD3gBXJjND0VJ z23EfXs(mS$^D;O)x(+T0Q&0$Gl}%gly7`Mw^Bj+`f&eL2@7JQjYjwDt4moXp$h7ia m{sM2&e$N~&tK6u0rzb~*?R{FtsB7SAq7>|J^hSh1r6Nk%w1VITk?0OkMyB7H_CfJiZePeyoTHHTC=hf+6)R7QbgH;h&}i&$oE zd`W|4J&jmBjafmATWWEDLycW*bAd&UTyk-TM3PiTk6lBOTSt*tb8?7pc7#NgR7aCo zb99JBl~r_fh(?oPNt0P~b%;unS#))XNtIYklU+)cSWJ^%Op{|ylU_`dWJ#4{O_X0z zm0D4iV^Wo2QI%v;m10qqW>l72PMBm?mS0wuU{jZ8P?=>@muOj+UQnD}R+ws2nrl~> zY*v|SQ=MRAkbG5~VpW@JRhw&8onmE?ep;GsSe$QOo?u&^Zd;yjU!P!Ho^gYPm4k(r zT%u=QpmAQHa$cfsXrNnTnbg*%jm!_7Ori7}NfVOmjwseQB znVq1lhPZd5r?HN`eUH9_C zX>@2HRA^-&M@dak03rDV2><|m04x9i001BWAOHXeg#h^o97wRB!Gj1BDqP60p~Hs| zBTAe|v7*I`7z@@BxWqz~jC?ZaA;Kn4njsc5s8kt1%#|?#QVJA_VuqO(C?az6_u;W6hg6k!B1!v?rgRJ1XJ?1a z$cmnqA3QxhZb4z;iK<*)zq*XN}LEJuxW{ZoCsszg)mOp=L{`~3r|NlS3C>RZaAr%4# z4jfSY$->A0%m_LlhlBD21IHYO|D1o!9h(laGYj+iC_HR(=HwLN)2V1|bmbOO^Y}CU zz(Ut#oFKjqhya2?vwTz7kPvXkf4g0F{(cC;$Ke literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/combine_disabled.gif b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/combine_disabled.gif new file mode 100644 index 0000000000000000000000000000000000000000..d23c22d7e146f32259f4b36e6dc84b137584db4e GIT binary patch literal 135 zcmZ?wbhEHb6krfwn8?7u@Sow}p`#zZeEt9bzv538PM`>b4u}BBGjK#OFmlLvYAZWzvu{-`eq28PUe%&URf`_h zE_+tJ_)+7$GZ*iFyZ`d%)3?7~fBgOG!>{*W{=EDA+cayJS@s^YtlehWyG=89nr7}W zN#A0YwaYYPyJ_Zj(~NDV8Cy-#Hk+hvGEUuKoU-01d5uxhYQuyThKVc8vv--KZZu9= zXPms&C~=ih(ki3Gm4*q+4HK4G9!EP7DB;9l;e)3wW=S1);7wfIrh;)iuBUev7savxPMept2WLBpz- zl{1dl%syE&>qOV`OMR=aPTg>8*4Df8cRgIP@5#zT&(|J%z4qv<&8OaOJM(VGx%ay- zyx(`}!=bC6_Fi~%{N|U#SKnWJ@cqL5ZxaB{6^aMg)EY~kcr0o;F`@iBAmmD4aw zcJ%}n9z5H~%p;Lf_({d9QHiauL$Pq7?^La@XLgPYmUIX*IUU;Y;DB1=lJg6knHiIv z4oIoD>#dAiD>aedUN>b$V8Mw=Z*OlCe4AmQ>^e(#W#oFdmc!gVTA}gGu~I3T#cHQ!G59$tXaqa9wDE_stuT1lYXDaDOc($F literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/hideedges.gif b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/hideedges.gif new file mode 100644 index 0000000000000000000000000000000000000000..b75bc19602e73fef574d2c720ced1f45597ede5e GIT binary patch literal 146 zcmZ?wbhEHb6krfw*v!DtJ#96kTh-6M|F3@j|MB<#H^2VB`~Cmh-~Ye<|NrwJ1Q<|( z;!hSv1_mw$9gr}{3-V1@KYulMPx|oX>(Af6|Ni^;=kMP`2M_-H_wWDz{|p#F z@h1x-0|O_64oC)M1_O(|!AZ~6do6As-fe%?ut$Y6C~3jESC*YFI>*l#3CO9ZMHMIyZTV#AtU^vpk#^s=p@F3|BGm}6?i$$V}=VVUx6)`6_shn!oi)ZTS fEJ|>k!YyrQqB(Jq>tspuG8fLEB|a^Tj11NQ`cymt literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/showedges.gif b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/showedges.gif new file mode 100644 index 0000000000000000000000000000000000000000..b2f45306c93ac4efc3fe2976868705e5f2645d43 GIT binary patch literal 153 zcmZ?wbhEHb6krfw*v!DtJ#96kTh-6M|F3@j|MB<#H^2VB`~Cm(pa0+f{{Qv=|DXRL zz<>l4f3h$#FmN;IfW$y%FtE54ob+71*CP7BZgx?F9u>}@q=js&s{9ryvNz}KndI^2 ui{At8zyuE-nFggJ9cf&=ECEs>4Kgb0wq!3_^+Jm+?#c7TnzO|P8LR;|Qb2G3 literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/showedges_disabled.gif b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/showedges_disabled.gif new file mode 100644 index 0000000000000000000000000000000000000000..bc37b7a4e52c174942439d0884daf609e7f49c0c GIT binary patch literal 153 zcmZ?wbhEHb6krfw*v!E2>-V1@KYulMPx|oX>(Af6|Ni^;=kMR&fBqagc<|r9fB*mg zXFvstKUo+V7`Pa8K*At17+Bl_PI|81YY}~9x4WoOj|yi{(gLnkRenl}+|53FCULy^ u!uNn{VuFW=OoP(ljx;V_o&c#U4KgC@wq!4y^+Js;?#c7TnzKa(8LR=xx<0f3 literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/split.gif b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/split.gif new file mode 100644 index 0000000000000000000000000000000000000000..68d1311e777dbd49e00b57aaa529e35f388893f8 GIT binary patch literal 204 zcmZ?wbhEHb6krfw*v!Dd@Sh>StfhO}>Z_mszxnn5-S7V&fB*mX_y5no|9}1e|L6aI zpb})D_>+Yb#Mc24ATt;^su&nKWIQ%JaA4#RR$~!Zu+V{#S;=dPMnhslJ2OYdofUx& z4UFQ94mu8-R6ILa6s00g7AqbN|L(_bRdM=Hd#*G&jyRmL~ tvvR@8%gelC?Lt-b4u}BBGjK#PFmlLvYWXB^NIWy_Z%M~z+{#_J{uzw&jJQUHpf;mn->cXTwEe2s#eftl67>Y|5~r5tF|UJ QvhnWHa`~XI00~BG08ubc%K!iX literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/zoomout.gif b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/icons/zoomout.gif new file mode 100644 index 0000000000000000000000000000000000000000..6dc4cbfa0955a58b990078cea3737802df72f703 GIT binary patch literal 924 zcmZ?wbhEHb6krfw_|IU-NIWy_Z%M~Y(+}Mqz4SlJS}{>6#<7Bmw4G~`Ft>0d8I34 YVcguP((I$myiygR9S`CiAR%ZC0IwEPJpcdz literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/layer.xml b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/layer.xml new file mode 100644 index 000000000000..99109c49b015 --- /dev/null +++ b/visualizer/C1Visualizer/ControlFlowEditor/src/main/resources/at/ssw/visualizer/cfg/layer.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/DataFlowEditor/pom.xml b/visualizer/C1Visualizer/DataFlowEditor/pom.xml new file mode 100644 index 000000000000..38323aa321a6 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowEditor/pom.xml @@ -0,0 +1,137 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + DataFlowEditor + 1.13-SNAPSHOT + nbm + DataFlowEditor + + UTF-8 + + + + at.ssw.visualizer + GraphLayoutImpl + ${project.version} + + + at.ssw.visualizer + GraphLayoutAPI + ${project.version} + + + at.ssw.visualizer + VisualizerUI + ${project.version} + + + at.ssw.visualizer + DataFlowGraph + ${project.version} + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + org.apache.xmlgraphics + batik-dom + ${batik.version} + + + org.apache.xmlgraphics + batik-svggen + ${batik.version} + + + org.netbeans.api + org-netbeans-api-visual + ${netbeans.version} + + + org.netbeans.api + org-openide-awt + ${netbeans.version} + + + org.netbeans.api + org-openide-util-ui + ${netbeans.version} + + + org.netbeans.api + org-openide-loaders + ${netbeans.version} + + + org.netbeans.api + org-openide-nodes + ${netbeans.version} + + + org.netbeans.api + org-openide-text + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.DataFlowEditor + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/java/at/ssw/visualizer/dataflow/action/ShowDataFlowEditorAction.java b/visualizer/C1Visualizer/DataFlowEditor/src/main/java/at/ssw/visualizer/dataflow/action/ShowDataFlowEditorAction.java new file mode 100644 index 000000000000..ff7f64b61695 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowEditor/src/main/java/at/ssw/visualizer/dataflow/action/ShowDataFlowEditorAction.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.action; + +import at.ssw.visualizer.core.focus.Focus; +import at.ssw.visualizer.dataflow.editor.DFEditorSupport; +import at.ssw.visualizer.dataflow.editor.DataFlowEditorTopComponent; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import org.openide.nodes.Node; +import org.openide.util.HelpCtx; +import org.openide.util.actions.CookieAction; + +/** + * This is the action that is hooked to an compilation-x-element within the + * filesystem menu. Essentially it extracts the ControlFlowGraph from the + * selected node and passes it to the DFEditorSupport bringing the + * viewer to the front. + * + * @author Stefan Loidl + * @author Christian Wimmer + */ +public final class ShowDataFlowEditorAction extends CookieAction { + private static final String ICON_PATH = "at/ssw/visualizer/dataflow/icons/dfg.gif"; + + /* + * The Action extracts the Control- Flow- Graph- Data- Object from the nodes which + * contain information for data flow too. + */ + protected void performAction(Node[] activatedNodes) { + ControlFlowGraph cfg = activatedNodes[0].getLookup().lookup(ControlFlowGraph.class); + if (!Focus.findEditor(DataFlowEditorTopComponent.class, cfg)) { + DFEditorSupport editor = new DFEditorSupport(cfg); + editor.open(); + } + } + + /* + * Defines when the context menu item triggering this action should be + * enabled (one element has to be chose and this elements has to have an + * ControlFlowGraph Cookie!- moreover HIR Codes has to be present within + * the node) + */ + @Override + protected boolean enable(Node[] activatedNodes) { + if (!super.enable(activatedNodes)) { + return false; + } + ControlFlowGraph cfg = activatedNodes[0].getLookup().lookup(ControlFlowGraph.class); + return cfg.hasHir(); + } + + public String getName() { + return "Open Data Flow Graph"; + } + + @Override + protected String iconResource() { + return ICON_PATH; + } + + protected int mode() { + return CookieAction.MODE_EXACTLY_ONE; + } + + protected Class[] cookieClasses() { + return new Class[]{ControlFlowGraph.class}; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + protected boolean asynchronous() { + return false; + } +} diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/java/at/ssw/visualizer/dataflow/editor/DFEditorSupport.java b/visualizer/C1Visualizer/DataFlowEditor/src/main/java/at/ssw/visualizer/dataflow/editor/DFEditorSupport.java new file mode 100644 index 000000000000..487f76ddddc7 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowEditor/src/main/java/at/ssw/visualizer/dataflow/editor/DFEditorSupport.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.editor; + +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.beans.VetoableChangeListener; +import java.beans.VetoableChangeSupport; +import java.io.IOException; +import org.openide.cookies.OpenCookie; +import org.openide.windows.CloneableOpenSupport; +import org.openide.windows.CloneableTopComponent; + +/** + * This class implements a cookie for opening the Data Flow Editor + * + * @author Stefan Loidl + * @author Christian Wimmer + */ +public class DFEditorSupport extends CloneableOpenSupport implements OpenCookie { + private ControlFlowGraph cfg; + + public DFEditorSupport(ControlFlowGraph cfg) { + super(new Env()); + ((Env) env).editorSupport = this; + this.cfg = cfg; + } + + protected CloneableTopComponent createCloneableTopComponent() { + return new DataFlowEditorTopComponent(cfg); + } + + public String messageOpened() { + return "Opened " + cfg.getCompilation().getMethod() + " - " + cfg.getName(); + } + + public String messageOpening() { + return "Opening " + cfg.getCompilation().getMethod() + " - " + cfg.getName(); + } + + + public static class Env implements CloneableOpenSupport.Env { + private PropertyChangeSupport prop = new PropertyChangeSupport(this); + private VetoableChangeSupport veto = new VetoableChangeSupport(this); + private DFEditorSupport editorSupport; + + public boolean isValid() { + return true; + } + + public boolean isModified() { + return false; + } + + public void markModified() throws IOException { + throw new IOException("Editor is readonly"); + } + + public void unmarkModified() { + // Nothing to do. + } + + public CloneableOpenSupport findCloneableOpenSupport() { + return editorSupport; + } + + public void addPropertyChangeListener(PropertyChangeListener l) { + prop.addPropertyChangeListener(l); + } + + public void removePropertyChangeListener(PropertyChangeListener l) { + prop.removePropertyChangeListener(l); + } + + public void addVetoableChangeListener(VetoableChangeListener l) { + veto.addVetoableChangeListener(l); + } + + public void removeVetoableChangeListener(VetoableChangeListener l) { + veto.removeVetoableChangeListener(l); + } + } +} diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/java/at/ssw/visualizer/dataflow/editor/DataFlowEditorTopComponent.java b/visualizer/C1Visualizer/DataFlowEditor/src/main/java/at/ssw/visualizer/dataflow/editor/DataFlowEditorTopComponent.java new file mode 100644 index 000000000000..a5825632ff87 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowEditor/src/main/java/at/ssw/visualizer/dataflow/editor/DataFlowEditorTopComponent.java @@ -0,0 +1,990 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.editor; + +import at.ssw.graphanalyzer.positioning.HierarchicalLayoutManager; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.dataflow.attributes.InvisibleNeighbourAttribute; +import at.ssw.visualizer.dataflow.attributes.TBExpandAllAttribute; +import at.ssw.visualizer.dataflow.attributes.TBShowBlockAttribute; +import at.ssw.visualizer.dataflow.graph.InscribeNodeWidget; +import at.ssw.visualizer.dataflow.graph.InstructionNodeWidget; +import at.ssw.dataflow.layout.CompoundForceLayouter; +import at.ssw.dataflow.layout.CompoundHierarchicalNodesLayouter; +import at.ssw.dataflow.layout.ExternalGraphLayoutWrapper; +import at.ssw.dataflow.layout.ForceLayouter; +import at.ssw.dataflow.layout.HierarchicalNodesLayouter; +import at.ssw.visualizer.dataflow.graph.InstructionNodeGraphScene; +import at.ssw.visualizer.dataflow.instructions.Instruction; +import at.ssw.visualizer.dataflow.instructions.InstructionSet; +import at.ssw.visualizer.dataflow.instructions.InstructionSetGenerator; +import at.ssw.dataflow.layout.MagneticSpringForceLayouter; +import at.ssw.dataflow.options.OptionEditor; +import at.ssw.visualizer.core.selection.Selection; +import at.ssw.visualizer.core.selection.SelectionManager; +import at.ssw.visualizer.core.selection.SelectionProvider; +import at.ssw.visualizer.model.cfg.State; +import at.ssw.visualizer.model.cfg.StateEntry; +import at.ssw.visualizer.dataflow.graph.InstructionSceneListener; +import at.ssw.visualizer.model.cfg.IRInstruction; +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JToggleButton; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.filechooser.FileFilter; +import org.openide.awt.Toolbar; +import org.openide.util.ImageUtilities; +import org.openide.windows.CloneableTopComponent; +import org.openide.windows.TopComponent; + +import org.apache.batik.dom.GenericDOMImplementation; +import org.apache.batik.svggen.SVGGeneratorContext; +import org.apache.batik.svggen.SVGGraphics2D; +import org.w3c.dom.DOMImplementation; +import java.io.*; +import java.awt.Graphics2D; + +/** + * Top component which displays the data-flow via the netbeans graph framework. + * + * @author Stefan Loidl + */ +final public class DataFlowEditorTopComponent extends CloneableTopComponent implements SelectionProvider { + //Scene visualizing the data-flow + private InstructionNodeGraphScene scene = null; + + //Datasources of the editor + private ControlFlowGraph cfg; + + // Management of the application global selection + private Selection selection; + private boolean selectionUpdating; + private BasicBlock[] curBlocks; + + //Buttons + private JButton BUTHideConstant; + private JToggleButton BUTShowBlocks; + private JToggleButton BUTExpandAll; + private JButton BUTHideParameter; + private JToggleButton BUTUseHierLayout; + private JToggleButton BUTUseCompoundLayout; + private JToggleButton BUTUseForceLayout; + private JToggleButton BUTUseDirectedForceLayout; + private JToggleButton BUTUseCompoundForceLayout; + private JToggleButton BUTUseHighlightClustering; + private JToggleButton BUTForceAutoLayout; + private JToggleButton BUTClusterLinkGrayed; + private JToggleButton BUTLayoutInvisibleNodes; + private JToggleButton BUTClusterBorderVisible; + private JToggleButton BUTUseNodeAnimation; + private JToggleButton BUTUseCurrentNodePosition; + private JButton BUTDoLayout; + private JButton BUTLayoutOptions; + private JButton BUTShowConstant; + private JButton BUTShowParameter; + private JButton BUTShowAll; + private JButton BUTHideAll; + private JButton BUTShowPhi; + private JButton BUTHidePhi; + private JButton BUTShowOperation; + private JButton BUTHideOperation; + private JButton BUTExportGraph; + private JToggleButton BUTUseAdvancedHierLayout; + + //Button tooltips + private static final String ZOOMIN = "Zoom In"; + private static final String ZOOMOUT = "Zoom Out"; + private static final String HIDECONSTANT = "Hide Constants"; + private static final String SHOWBLOCKS = "Show Blocks"; + private static final String EXPANDALL = "Expand All"; + private static final String HIDEPARAMETER = "Hide Parameter"; + private static final String DOLAYOUT = "Do Layout"; + private static final String USEHIERLAYOUT = "Use Hierachical Layout"; + private static final String USECOMPLAYOUT = "Use Compound Layout"; + private static final String USEFORCELAYOUT = "Use Force Layout"; + private static final String USEDIRECTEDFORCELAYOUT = "Use Directed Force Layout"; + private static final String USECOMPOUNDFORCELAYOUT = "Use Compound Force Layout"; + private static final String USEHIGHLIGHTCLUSTERING = "Use Highlight Clustering"; + private static final String FORCEAUTOLAYOUT = "Force Auto Layout"; + private static final String CLUSTERLINKGRAYED = "Cluster Link Grayed"; + private static final String LAYOUTOPTIONS = "Layout Options..."; + private static final String LAYOUTINVISIBLENODES = "Layout Invisible Nodes"; + private static final String CLUSTERBORDERVISIBLE = "Cluster Border Visible"; + private static final String USENODEANIMATION = "Use Node Animation"; + private static final String USECURRENTNODEPOSITION = "The next layout cycle builds on the current node position"; + private static final String SHOWCONSTANT = "Show Constants"; + private static final String SHOWPARAMETER = "Show Parameter"; + private static final String SHOWALL = "Show All"; + private static final String HIDEALL = "Hide All"; + private static final String SHOWPHI = "Show Phi Functions"; + private static final String HIDEPHI = "Hide Phi Functions"; + private static final String SHOWOPERATION = "Show Operations"; + private static final String HIDEOPERATION = "Hide Operations"; + private static final String EXPORTGRAPH = "Export Graph To..."; + private static final String USEADVANCEDHIERLAYOUT = "Use Advanced Hierachical Layout"; + + //Layouter + private CompoundHierarchicalNodesLayouter compoundLayouter; + private HierarchicalNodesLayouter hierarchicalLayouter; + private ForceLayouter forceLayouter; + private CompoundForceLayouter compoundForceLayouter; + private MagneticSpringForceLayouter directedForceLayouter; + private ExternalGraphLayoutWrapper advancedhierarchicalLayouter; + + /** path to the icon used by the component and its open action */ + private static final String ICON_PATH = "at/ssw/visualizer/dataflow/icons/"; + private static final String ICON_ZOOMIN = "zoomin.gif"; + private static final String ICON_ZOOMOUT = "zoomout.gif"; + private static final String ICON_EDITOR = "dfg.gif"; + private static final String ICON_HIRACHICALLAYOUT = "arrangehier.gif"; + private static final String ICON_HIRACHICALLAYOUTADVANCED = "arrangehieradvanced.gif"; + private static final String ICON_FORCELAYOUT = "arrangeforce.gif"; + private static final String ICON_DIRECTEDFORCELAYOUT = "directedforce.gif"; + private static final String ICON_HIRACHICALLAYOUTCL = "arrangehiercluster.gif"; + private static final String ICON_FORCELAYOUTCL = "arrangeforcecluster.gif"; + private static final String ICON_EXPANDALL = "expall.gif"; + private static final String ICON_EXPANDBLOCKS = "expblocks.gif"; + private static final String ICON_OPTIONS = "options.gif"; + private static final String ICON_LAYOUT = "layout.gif"; + private static final String ICON_AUTOLAYOUT = "autolayout.gif"; + private static final String ICON_LAYOUTINV = "layoutinvisible.gif"; + private static final String ICON_ANIMATE = "animate.gif"; + private static final String ICON_CLUSTERHIGH = "clusterhigh.gif"; + private static final String ICON_LINKGRAY = "linkgrayed.gif"; + private static final String ICON_CLUSTERB = "cluster.gif"; + private static final String ICON_HIDEPARAM = "hideparam.gif"; + private static final String ICON_HIDECONSTANT = "hideconst.gif"; + private static final String ICON_SHOWPARAM = "showparam.gif"; + private static final String ICON_SHOWCONSTANT = "showconst.gif"; + private static final String ICON_HIDEALL = "hideall.gif"; + private static final String ICON_SHOWALL = "showall.gif"; + private static final String ICON_HIDEPHI = "hidephi.gif"; + private static final String ICON_SHOWPHI = "showphi.gif"; + private static final String ICON_HIDEOPERATION = "hideoperation.gif"; + private static final String ICON_SHOWOPERATION = "showoperation.gif"; + private static final String ICON_CURRENTNODE = "currentnode.gif"; + private static final String ICON_EXPORTGRAPH = "disk.gif"; + + //This view is used for the export of vector graphics + private JScrollPane scenePane; + + + /** + * Constructor + */ + public DataFlowEditorTopComponent(ControlFlowGraph cfg) { + this.cfg = cfg; + + setIcon(ImageUtilities.loadImage(ICON_PATH + ICON_EDITOR, true)); + setName(cfg.getCompilation().getShortName()); + setToolTipText(cfg.getCompilation().getMethod() + " - " + cfg.getName()); + + scene = new InstructionNodeGraphScene(); + scene.addInstructionSceneListener(instructionSceneListener); + + selection = new Selection(); + selection.put(cfg); + selection.put(scene); + selection.addChangeListener(selectionListener); + + scenePane = new JScrollPane(scene.createView()); + scenePane.setBorder(BorderFactory.createEmptyBorder()); + scenePane.setViewportBorder(BorderFactory.createEmptyBorder()); + JPanel scenePanel = new JPanel(); + scenePanel.setLayout(new BorderLayout()); + scenePanel.add(scenePane, BorderLayout.CENTER); + + //create the layouters + hierarchicalLayouter = new HierarchicalNodesLayouter(); + compoundLayouter = new CompoundHierarchicalNodesLayouter(); + forceLayouter = new ForceLayouter(); + compoundForceLayouter = new CompoundForceLayouter(); + directedForceLayouter = new MagneticSpringForceLayouter(); + advancedhierarchicalLayouter = new ExternalGraphLayoutWrapper(new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.NONE), false, false, false); + + scenePanel.add(createToolbar(), BorderLayout.NORTH); + + setLayout(new BorderLayout()); + add(scenePanel, BorderLayout.CENTER); + } + + public Selection getSelection() { + return selection; + } + + private ChangeListener selectionListener = new ChangeListener() { + public void stateChanged(ChangeEvent event) { + if (selectionUpdating) { + return; + } + selectionUpdating = true; + + BasicBlock[] newBlocks = selection.get(BasicBlock[].class); + if (newBlocks != null && newBlocks.length > 0 && !Arrays.equals(curBlocks, newBlocks)) { + HashSet instructions = new HashSet(); + for (BasicBlock b : newBlocks) { + for (IRInstruction i : b.getHirInstructions()) { + InstructionNodeWidget nw = scene.getNodeWidget(i.getValue(IRInstruction.HIR_NAME)); + //Some instructions are within the states of more than one block + if (nw != null) { + instructions.add(nw.getInstruction()); + } + } + for (State s : b.getStates()) { + for (StateEntry se : s.getEntries()) { + InstructionNodeWidget nw = scene.getNodeWidget(se.getName()); + //Some instructions are within the states of more than one block + if (nw != null && b.getName() != null && b.getName().equals(nw.getInstruction().getSourceBlock())) { + instructions.add(nw.getInstruction()); + } + } + } + } + scene.setSelectedObjects(instructions); + scene.refreshAll(); + scene.validate(); + } + curBlocks = newBlocks; + selectionUpdating = false; + } + }; + + + private InstructionSceneListener instructionSceneListener = new InstructionSceneListener() { + public void selectionChanged(Set w) { + if (selectionUpdating) { + return; + } + selectionUpdating = true; + + List newBlocks = new ArrayList(); + for (InstructionNodeWidget widget : w) { + String name = widget.getInstruction().getSourceBlock(); + //Sometimes block values are not filled + if (name == null) { + continue; + } + BasicBlock b = cfg.getBasicBlockByName(name); + if (b != null) { + newBlocks.add(b); + } + } + + curBlocks = newBlocks.toArray(new BasicBlock[newBlocks.size()]); + selection.put(curBlocks); + selectionUpdating = false; + } + + //Not used here + public void doubleClicked(InstructionNodeWidget w) { + } + public void updateNodeData() { + } + }; + + + /** + * This method activates the data source of the viewer. + * BasicBlocks are used as basic granularity for this + * reason. The Scene is also built within this method. + */ + private void activateDataSource() { + scene.validate(); + + InstructionSet iset = InstructionSetGenerator.generateFromBlocks(cfg.getBasicBlocks()); + Instruction[] instructions = iset.getInstructions(); + //No control flow instructions are shown in the graph + instructions = InstructionSet.filterInstructionType(instructions, Instruction.InstructionType.CONTROLFLOW); + + scene.addInstructions(instructions); + + for (Instruction inst : instructions) { + InstructionNodeWidget inw = scene.getNodeWidget(inst.getID()); + + //Hide Constants + if (inst.getInstructionType() == Instruction.InstructionType.CONSTANT) { + //inw.addNodeAttribute(new TBInvisibilityAttribute(BUTHideConstant)); + //Show constants in successor nodes + for (Instruction i : inst.getSuccessors()) { + InstructionNodeWidget widget = scene.getNodeWidget(i.getID()); + if (widget != null) { + InscribeNodeWidget newW = new InscribeNodeWidget(inst, scene); + widget.addNodeAttribute(new InvisibleNeighbourAttribute(newW, inw, false)); + } + } + } + + //Hide Parameters + if (inst.getInstructionType() == Instruction.InstructionType.PARAMETER) { + //inw.addNodeAttribute(new TBInvisibilityAttribute(BUTHideParameter)); + //Show constants in successor nodes + for (Instruction i : inst.getSuccessors()) { + InstructionNodeWidget widget = scene.getNodeWidget(i.getID()); + if (widget != null) { + InscribeNodeWidget newW = new InscribeNodeWidget(inst, scene); + widget.addNodeAttribute(new InvisibleNeighbourAttribute(newW, inw, false)); + } + } + } + + //Show Blocks + inw.addNodeAttribute(new TBShowBlockAttribute(BUTShowBlocks)); + + //Show Instructionstring + inw.addNodeAttribute(new TBExpandAllAttribute(BUTExpandAll)); + } + + //Assign standard layouter + scene.setExternalLayouter(hierarchicalLayouter); + scene.refreshAll(); + scene.layout(); + scene.validate(); + } + + @Override + protected void componentOpened() { + super.componentOpened(); + activateDataSource(); + } + + /* overwritten from parent- called if editor is activated*/ + @Override + protected void componentActivated() { + super.componentActivated(); + SelectionManager.getDefault().setSelection(selection); + } + + /* overwritten from parent- called if editor is closed*/ + @Override + protected void componentClosed() { + super.componentClosed(); + SelectionManager.getDefault().removeSelection(selection); + } + + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_NEVER; + } + + @Override + protected CloneableTopComponent createClonedObject() { + return new DataFlowEditorTopComponent(cfg); + } + + // + /** Creates the toolbar */ + private Toolbar createToolbar() { + Toolbar toolBar = new Toolbar(); + toolBar.setBorder((Border) UIManager.get("Nb.Editor.Toolbar.border")); + + JButton zoomIn = new JButton(); + zoomIn.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_ZOOMIN, true))); + zoomIn.setToolTipText(ZOOMIN); + zoomIn.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + scene.setZoomFactor(scene.getZoomFactor() * 1.2); + scene.validate(); + } + }); + + JButton zoomOut = new JButton(); + zoomOut.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_ZOOMOUT, true))); + zoomOut.setToolTipText(ZOOMOUT); + zoomOut.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + scene.setZoomFactor(scene.getZoomFactor() / 1.2); + scene.validate(); + } + }); + + BUTShowAll = new JButton(); + BUTShowAll.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_SHOWALL, true))); + BUTShowAll.setToolTipText(SHOWALL); + BUTShowAll.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + scene.handleSetVisibilityNodeType(true, null); + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + } + }); + + BUTHideAll = new JButton(); + BUTHideAll.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_HIDEALL, true))); + BUTHideAll.setToolTipText(HIDEALL); + BUTHideAll.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + scene.handleSetVisibilityNodeType(false, null); + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + } + }); + + BUTHidePhi = new JButton(); + BUTHidePhi.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_HIDEPHI, true))); + BUTHidePhi.setToolTipText(HIDEPHI); + BUTHidePhi.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + scene.handleSetVisibilityNodeType(false, Instruction.InstructionType.PHI); + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + } + }); + + BUTShowPhi = new JButton(); + BUTShowPhi.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_SHOWPHI, true))); + BUTShowPhi.setToolTipText(SHOWPHI); + BUTShowPhi.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + scene.handleSetVisibilityNodeType(true, Instruction.InstructionType.PHI); + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + } + }); + + BUTShowOperation = new JButton(); + BUTShowOperation.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_SHOWOPERATION, true))); + BUTShowOperation.setToolTipText(SHOWOPERATION); + BUTShowOperation.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + scene.handleSetVisibilityNodeType(true, Instruction.InstructionType.OPERATION); + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + } + }); + + BUTHideOperation = new JButton(); + BUTHideOperation.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_HIDEOPERATION, true))); + BUTHideOperation.setToolTipText(HIDEOPERATION); + BUTHideOperation.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + scene.handleSetVisibilityNodeType(false, Instruction.InstructionType.OPERATION); + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + } + }); + + BUTShowConstant = new JButton(); + BUTShowConstant.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_SHOWCONSTANT, true))); + BUTShowConstant.setToolTipText(SHOWCONSTANT); + BUTShowConstant.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + scene.handleSetVisibilityNodeType(true, Instruction.InstructionType.CONSTANT); + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + } + }); + + BUTShowParameter = new JButton(); + BUTShowParameter.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_SHOWPARAM, true))); + BUTShowParameter.setToolTipText(SHOWPARAMETER); + BUTShowParameter.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + scene.handleSetVisibilityNodeType(true, Instruction.InstructionType.PARAMETER); + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + } + }); + + BUTHideConstant = new JButton(); + BUTHideConstant.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_HIDECONSTANT, true))); + BUTHideConstant.setToolTipText(HIDECONSTANT); + BUTHideConstant.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + scene.handleSetVisibilityNodeType(false, Instruction.InstructionType.CONSTANT); + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + } + }); + + BUTShowBlocks = new JToggleButton(); + BUTShowBlocks.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_EXPANDBLOCKS, true))); + BUTShowBlocks.setToolTipText(SHOWBLOCKS); + BUTShowBlocks.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + } + }); + + BUTExpandAll = new JToggleButton(); + BUTExpandAll.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_EXPANDALL, true))); + BUTExpandAll.setToolTipText(EXPANDALL); + BUTExpandAll.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + } + }); + + BUTHideParameter = new JButton(); + BUTHideParameter.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_HIDEPARAM, true))); + BUTHideParameter.setToolTipText(HIDEPARAMETER); + BUTHideParameter.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + scene.handleSetVisibilityNodeType(false, Instruction.InstructionType.PARAMETER); + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + } + }); + + BUTDoLayout = new JButton(); + BUTDoLayout.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_LAYOUT, true))); + BUTDoLayout.setToolTipText(DOLAYOUT); + BUTDoLayout.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + scene.layout(); + scene.refreshAll(); + scene.validate(); + } + }); + + BUTLayoutOptions = new JButton(); + BUTLayoutOptions.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_OPTIONS, true))); + BUTLayoutOptions.setToolTipText(LAYOUTOPTIONS); + BUTLayoutOptions.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + OptionEditor ed = new OptionEditor(scene.getExternalLayouter()); + ed.setVisible(true); + scene.refreshAll(); + scene.validate(); + scene.layout(); + } + }); + + BUTUseHierLayout = new JToggleButton(); + BUTUseHierLayout.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_HIRACHICALLAYOUT, true))); + BUTUseHierLayout.setToolTipText(USEHIERLAYOUT); + BUTUseHierLayout.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + changeLayouter((JToggleButton) e.getSource()); + } + }); + //Default selected button + BUTUseHierLayout.setSelected(true); + + BUTUseAdvancedHierLayout = new JToggleButton(); + BUTUseAdvancedHierLayout.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_HIRACHICALLAYOUTADVANCED, true))); + BUTUseAdvancedHierLayout.setToolTipText(USEADVANCEDHIERLAYOUT); + BUTUseAdvancedHierLayout.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + changeLayouter((JToggleButton) e.getSource()); + } + }); + + + //BUTTON: Use Compound Layout + BUTUseCompoundLayout = new JToggleButton(); + BUTUseCompoundLayout.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_HIRACHICALLAYOUTCL, true))); + BUTUseCompoundLayout.setToolTipText(USECOMPLAYOUT); + BUTUseCompoundLayout.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + changeLayouter((JToggleButton) e.getSource()); + } + }); + + //BUTTON: Use Force Layout + BUTUseForceLayout = new JToggleButton(); + BUTUseForceLayout.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_FORCELAYOUT, true))); + BUTUseForceLayout.setToolTipText(USEFORCELAYOUT); + BUTUseForceLayout.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + changeLayouter((JToggleButton) e.getSource()); + } + }); + + //BUTTON: Use Directed Force Layout + BUTUseDirectedForceLayout = new JToggleButton(); + BUTUseDirectedForceLayout.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_DIRECTEDFORCELAYOUT, true))); + BUTUseDirectedForceLayout.setToolTipText(USEDIRECTEDFORCELAYOUT); + BUTUseDirectedForceLayout.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + changeLayouter((JToggleButton) e.getSource()); + } + }); + + //BUTTON: Use Compound Force Layout + BUTUseCompoundForceLayout = new JToggleButton(); + BUTUseCompoundForceLayout.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_FORCELAYOUTCL, true))); + BUTUseCompoundForceLayout.setToolTipText(USECOMPOUNDFORCELAYOUT); + BUTUseCompoundForceLayout.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + changeLayouter((JToggleButton) e.getSource()); + } + }); + + //BUTTON: Highlight Clustering + BUTUseHighlightClustering = new JToggleButton(); + BUTUseHighlightClustering.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_CLUSTERHIGH, true))); + BUTUseHighlightClustering.setToolTipText(USEHIGHLIGHTCLUSTERING); + BUTUseHighlightClustering.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + boolean b = BUTUseHighlightClustering.isSelected(); + scene.setHighlightClustering(b); + scene.layout(); + } + }); + BUTUseHighlightClustering.setEnabled(false); + + //BUTTON: Auto Layout + BUTForceAutoLayout = new JToggleButton(); + BUTForceAutoLayout.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_AUTOLAYOUT, true))); + BUTForceAutoLayout.setToolTipText(FORCEAUTOLAYOUT); + BUTForceAutoLayout.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + boolean b = BUTForceAutoLayout.isSelected(); + scene.setAutoLayout(b); + } + }); + BUTForceAutoLayout.setSelected(scene.isAutoLayout()); + + + //BUTTON: Cluster Link Grayed + BUTClusterLinkGrayed = new JToggleButton(); + BUTClusterLinkGrayed.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_LINKGRAY, true))); + BUTClusterLinkGrayed.setToolTipText(CLUSTERLINKGRAYED); + BUTClusterLinkGrayed.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + boolean b = BUTClusterLinkGrayed.isSelected(); + scene.setInterClusterLinkGrayed(b); + scene.validate(); + } + }); + BUTClusterLinkGrayed.setSelected(scene.isInterClusterLinkGrayed()); + BUTClusterLinkGrayed.setEnabled(false); + + //BUTTON: Cluster Border Visible + BUTClusterBorderVisible = new JToggleButton(); + BUTClusterBorderVisible.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_CLUSTERB, true))); + BUTClusterBorderVisible.setToolTipText(CLUSTERBORDERVISIBLE); + BUTClusterBorderVisible.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + boolean b = BUTClusterBorderVisible.isSelected(); + scene.setClusterBordersVisible(b); + scene.validate(); + } + }); + BUTClusterBorderVisible.setSelected(scene.isClusterBordersVisible()); + BUTClusterBorderVisible.setEnabled(false); + + //BUTTON: Layout Invisible Nodes + BUTLayoutInvisibleNodes = new JToggleButton(); + BUTLayoutInvisibleNodes.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_LAYOUTINV, true))); + BUTLayoutInvisibleNodes.setToolTipText(LAYOUTINVISIBLENODES); + BUTLayoutInvisibleNodes.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + boolean b = BUTLayoutInvisibleNodes.isSelected(); + scene.setLayoutInvisibleNodes(b); + scene.layout(); + scene.validate(); + } + }); + BUTLayoutInvisibleNodes.setSelected(scene.isLayoutInvisibleNodes()); + + //BUTTON: Use Node Animation + BUTUseNodeAnimation = new JToggleButton(); + BUTUseNodeAnimation.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_ANIMATE, true))); + BUTUseNodeAnimation.setToolTipText(USENODEANIMATION); + BUTUseNodeAnimation.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + boolean b = BUTUseNodeAnimation.isSelected(); + scene.setNodeAnimation(b); + } + }); + BUTUseNodeAnimation.setSelected(scene.isNodeAnimation()); + + //BUTTON: Use current node position + BUTUseCurrentNodePosition = new JToggleButton(); + BUTUseCurrentNodePosition.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_CURRENTNODE, true))); + BUTUseCurrentNodePosition.setToolTipText(USECURRENTNODEPOSITION); + BUTUseCurrentNodePosition.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + boolean b = BUTUseCurrentNodePosition.isSelected(); + scene.setUseCurrentNodePositions(b); + } + }); + BUTUseCurrentNodePosition.setSelected(scene.isUseCurrentNodePositions()); + + // Button: Export graph + BUTExportGraph = new JButton(); + BUTExportGraph.setIcon(new ImageIcon(ImageUtilities.loadImage(ICON_PATH + ICON_EXPORTGRAPH, true))); + BUTExportGraph.setToolTipText(EXPORTGRAPH); + BUTExportGraph.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + JFileChooser fc = new JFileChooser(); + fc.setAcceptAllFileFilterUsed(false); + fc.setDialogTitle(EXPORTGRAPH); + fc.addChoosableFileFilter(new SimpleFileFilter("Scaleable Vector Format (*.svg)", ".svg")); + fc.addChoosableFileFilter(new SimpleFileFilter("Graph Modelling Language (*.gml)", ".gml")); + fc.setMultiSelectionEnabled(false); + fc.showSaveDialog(scenePane); + //file selected? + if (fc.getSelectedFile() != null) { + String fileName = fc.getSelectedFile().getAbsolutePath().toLowerCase(); + SimpleFileFilter filter = (SimpleFileFilter) fc.getFileFilter(); + if (!fileName.endsWith(filter.getFilter())) { + fileName += filter.getFilter(); + } + + if (fileName.endsWith("svg")) { + File f = new File(fileName); + DOMImplementation dom = GenericDOMImplementation.getDOMImplementation(); + org.w3c.dom.Document document = dom.createDocument("http://www.w3.org/2000/svg", "svg", null); + SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(document); + ctx.setEmbeddedFontsOn(true); + Graphics2D svgGenerator = new SVGGraphics2D(ctx, true); + scenePane.paint(svgGenerator); + FileOutputStream os = null; + try { + os = new FileOutputStream(f); + Writer out = new OutputStreamWriter(os, "UTF-8"); + assert svgGenerator instanceof SVGGraphics2D; + SVGGraphics2D svgGraphics = (SVGGraphics2D)svgGenerator; + svgGraphics.stream(out, true); + } catch (IOException exc) { + // NotifyDescriptor message = new NotifyDescriptor.Message( + // Bundle.EXPORT_BATIK_ErrorExportingSVG(e.getLocalizedMessage()), NotifyDescriptor.ERROR_MESSAGE); + // DialogDisplayer.getDefault().notifyLater(message); + } finally { + if (os != null) { + try { + os.close(); + } catch (IOException exc) { + } + } + } + } + + if (fileName.endsWith("gml")) { + try { + File f = new File(fileName); + scene.exportToGMLFile(f); + } catch (Exception ex) { + } + } + } + } + }); + + toolBar.add(zoomIn); + toolBar.add(zoomOut); + toolBar.addSeparator(); + + // JLabel lab=new JLabel("Hide:"); + // Font font=lab.getFont().deriveFont(Font.BOLD); + // lab.setFont(font); + // toolBar.add(lab); + toolBar.add(BUTHideConstant); + toolBar.add(BUTHideParameter); + toolBar.add(BUTHidePhi); + toolBar.add(BUTHideOperation); + toolBar.add(BUTHideAll); + + toolBar.addSeparator(); + + toolBar.add(BUTShowConstant); + toolBar.add(BUTShowParameter); + toolBar.add(BUTShowPhi); + toolBar.add(BUTShowOperation); + toolBar.add(BUTShowAll); + + toolBar.addSeparator(); + + // lab=new JLabel("Expand:"); + // lab.setFont(font); + // toolBar.add(lab); + toolBar.add(BUTShowBlocks); + toolBar.add(BUTExpandAll); + toolBar.addSeparator(); + + // lab=new JLabel("Layout:"); + // lab.setFont(font); + //toolBar.add(lab); + toolBar.add(BUTUseHierLayout); + toolBar.add(BUTUseCompoundLayout); + toolBar.add(BUTUseForceLayout); + toolBar.add(BUTUseDirectedForceLayout); + toolBar.add(BUTUseCompoundForceLayout); + // [cwi] temporarily removed until new implementation of algorithm is integrated + // toolBar.add(BUTUseAdvancedHierLayout); + toolBar.addSeparator(); + + // lab=new JLabel("Misc:"); + // lab.setFont(font); + //toolBar.add(lab); + toolBar.add(BUTDoLayout); + toolBar.add(BUTForceAutoLayout); + toolBar.add(BUTUseCurrentNodePosition); + toolBar.add(BUTLayoutOptions); + toolBar.add(BUTLayoutInvisibleNodes); + toolBar.add(BUTUseNodeAnimation); + toolBar.addSeparator(); + + // lab=new JLabel("Clustering:"); + // lab.setFont(font); + //toolBar.add(lab); + toolBar.add(BUTUseHighlightClustering); + toolBar.add(BUTClusterLinkGrayed); + toolBar.add(BUTClusterBorderVisible); + toolBar.addSeparator(); + + toolBar.add(BUTExportGraph); + + return toolBar; + } + + /** Used if one of the layouter buttons was activated */ + private void changeLayouter(JToggleButton sender) { + BUTUseHierLayout.setSelected(false); + BUTUseCompoundLayout.setSelected(false); + BUTUseForceLayout.setSelected(false); + BUTUseCompoundForceLayout.setSelected(false); + BUTUseDirectedForceLayout.setSelected(false); + BUTUseAdvancedHierLayout.setSelected(false); + //doesn't trigger a action event + sender.setSelected(true); + + if (sender == BUTUseHierLayout) { + scene.setExternalLayouter(hierarchicalLayouter); + } + + if (sender == BUTUseCompoundLayout) { + scene.setExternalLayouter(compoundLayouter); + } + if (sender == BUTUseForceLayout) { + scene.setExternalLayouter(forceLayouter); + } + if (sender == BUTUseCompoundForceLayout) { + scene.setExternalLayouter(compoundForceLayouter); + } + if (sender == BUTUseAdvancedHierLayout) { + scene.setExternalLayouter(advancedhierarchicalLayouter); + } + if (sender == BUTUseDirectedForceLayout) { + scene.setExternalLayouter(directedForceLayouter); + } + boolean clustering = scene.getExternalLayouter().isClusteringSupported(); + BUTUseHighlightClustering.setEnabled(clustering); + BUTClusterLinkGrayed.setEnabled(clustering); + BUTClusterBorderVisible.setEnabled(clustering); + + scene.refreshAll(); + scene.layout(); + scene.validate(); + } + +/** + * Simple file filter implementation that filters all files that are no + * diretories and do not end with a specified filter string. + * This string can be used to get all files with specified type. + */ + class SimpleFileFilter extends FileFilter { + + private String desc = null; + private String filter = null; + + public SimpleFileFilter(String desc, String filter) { + this.filter = filter.toLowerCase(); + this.desc = desc; + } + + public boolean accept(File f) { + if (f == null) { + return false; + } + if (f.getName().toLowerCase().endsWith(filter)) { + return true; + } + if (f.isDirectory()) { + return true; + } + return false; + } + + public String getDescription() { + return desc; + } + + public String getFilter() { + return filter; + } + } + // +} diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/DataFlowEditor/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..ef9cea5e2300 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowEditor/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.dataflow +OpenIDE-Module-Layer: at/ssw/visualizer/dataflow/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/dataflow/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/Bundle.properties b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/Bundle.properties new file mode 100644 index 000000000000..dfbd1e4f271c --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Data Flow Editor diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/animate.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/animate.gif new file mode 100644 index 0000000000000000000000000000000000000000..164ca6926acf6d9ee94492a66dbb8edbc39e3538 GIT binary patch literal 310 zcmZ?wbhEHb6krfwxXJ(m|Ns9#e(J*2>$k7nxRcgYmD*I9*j1X?U8>gLq0#86)8b>= z7GTj9XxSd*GdVeGc7F7n0_RE50n<`LXJ>`Z&JLfG6FECCdR~6a{DQazg=Op7%GbA7 zZRko}TAa0{G;?`z>g>FrDKTM_<74KerTbz}&B)M>1LD$~Si3cW>Y$(iLnVz#U zqiRL=!eebf$ABG0ETQ<5g^_{5fI$bO0puqJwqS=D1s*z5{q8+3!VY}etHrw>iKs;^ z4@Hs7amkAi#SN2QE<81Zo0sL_<_96lit;ML1|fc|JRHmhoS_Ed S&is7_8CjF3&zS7UU=0A%18Teg literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/arrangeforce.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/arrangeforce.gif new file mode 100644 index 0000000000000000000000000000000000000000..34165b8de3a8e39b443ef2c0afa6baeb3d73096c GIT binary patch literal 322 zcmZ?wbhEHb6krfwxXJ(m|Ns9lC~wU#YdL!2+_f8bimTesUA&swRGHRWmDp99G@(qb z!NamW$Y*l0^Q7p|*;%2pv%_X&MbFC*o}M1JpfG4kOxWc3z^PFo)1nJk6(uc6E?ieo zvZ1hKO;PsB^qiF$RV%U=9%}>I0=A9HgyK&YMg|6D1|5*OAU`p%#UGeh;GrYc@A2?S zpeLWQtRVX%8ACg1fyJ+OwCMC{FM!3-kM+5a`eQxYd7wsHdUteRwZ_oCQT?)Yw)ma z5AvCu>^vztbaqzg?Ch`^S<&8BVMZj literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/arrangehier.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/arrangehier.gif new file mode 100644 index 0000000000000000000000000000000000000000..54260ed87053b11462d6da084f47af4272ccd61a GIT binary patch literal 320 zcmZ?wbhEHb6krfwxXQo~RG;~ufkCaoL!-gdvOUOoQgqX)}opmh}+k0m&Jl3}N!0`izPaZvS z?)16K45S0apDc_F42%pqAgv%jF|frRXfROF%xF8IlCp%$c2WRi`e#Xp$z9AR3_N2e z@;E3rSk<;KdH5>n&$3p5z|Wku>@2dY9%T7PY~!kNRuf_2%X3nVOBdvjP|;3j^wyFT Vom3tyS3N1RVFn|2Te>5IH2^GVY1RM$ literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/arrangehieradvanced.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/arrangehieradvanced.gif new file mode 100644 index 0000000000000000000000000000000000000000..1c399fd43aebaca0df4a70d518e417b5d2fa348e GIT binary patch literal 320 zcmZ?wbhEHb6krfwxXQrL9p%bk{(q)2?+jJ`IR?rrEOb}cxvy~andN7_R*;_<*kTVf7$|6Fw4G2%S;A#IDS$Ekv!ui1F6I*k zp0N{o9F!ZZYTK7Qe3kTPS*t+cXU0QgyFzW@LL literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/arrangehiercluster.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/arrangehiercluster.gif new file mode 100644 index 0000000000000000000000000000000000000000..bad94377581d90d852e153e1b3f84e09f3180720 GIT binary patch literal 332 zcmZ?wbhEHb6krfwxXJ(m|Ns9lt?t}=;5dWV{lbd2{IZs`#Dm) zC@h##88|g6WLk96lH{C4Ifd&AvX^F+Y$z;QUzoizJ!fS`)r#ze$J&6lgIz%;q4<-9 zk%2**K?kHCay{4yr2;7#x>(dK8577njrC-nPoF-1{P_O;`}gkM zyL7Xe$B!L5cJ%1cBS(%LK79Dlp+g4_9N4#S-`>4@ckkZ4YuBzFJ9cc|ym`~6 zP3zXJTf27cnl)=yu3Wid#fs(2moHtqbkU+k3l}b2uwcQwdGjVtoY>pj+uhyW(b3V~ z-rmyE(%jtK)YR12*jQCnRZvinlarH`m6ei`l9-qn5fKp{9v&1F6c7;L>+9>`;Q{j9 zAO?y*Sr{1@ycu*rZUn^%1N*)P@1|yN8Tpou78wo>?an4)QAuwFE^bY4Z9T&lURGX3 zWdR{AZ3%Xb7I_{aKHkNA!s62QEwV!5l4_<#in2-?7ERLfO8jD~Hce&BuR`nQbX>bU0wE z*b?XRp&+q|jgN&*LdBt@rAgenq=qB#!#sW`+XkMXJ?1a z$cmnqA3QxhZb4z;XJ?1a z$cmnqA3QxhZb4z;6#_pDWY5p zr~gt=n|4aNo7UO{y?S-@kYDz-yyXhst&waUedTF_dZK41cv$7BbSQm1 PX>fV9zIvmQB7-#m;nrA@ literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/currentnode.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/currentnode.gif new file mode 100644 index 0000000000000000000000000000000000000000..4a6908740f6bcf198e4e9a9679200331b23d3074 GIT binary patch literal 565 zcmZ?wbhEHb6krfwc*ek>7%!)sq)@WEKqW~*EmcW9O<5~jO*2zfCr4d3PgB1@%dl9- zs8rXaT;H+7x^#Cz<=&Fo{S{`F29|Zk7S%@94W{<37Iw|%b}bgpT{f=0cJBQS-cy{t zCOP>`aq*k(7C6TYj=FsuK4s7@dcYQ^4F&oZq6**kz2MkuWDye`=R>QgLOT}S|^<9oOr5h z%9-AlqgAa(tLyiaO*+#t;Z*zNGhNfp^-ezBJ@riQjB^ua0@3-2v(8VNb#CIEi&JKw zpFHp4wE35&FT6Z+@s-(gFHT-?dD^^-Q|Dfovgpdpg_mb6y*g+4wRtPAFZloeKLc4n z@h1x-14Ax@4oEjBP8irHHRLumx3spkceGoXbU4`CSX-F6xyrRV28Ly3WhACY`#VcE zI|b*YCMPAtM@9Ilh&2afXT`XiD#**KDoZrmg{H)so3`+Yx7fy|1@(01T1Q5E8lTU# z^a}Bjk-p{mVdBWd%*w$h Nz`=0f)-lX*KXV?u4+Gb@oH*QWm<1lVpnO>gfg`T z56ku-pUKJ2lcGasXNAtr4x5n`Jug3adV1V~!k{TJVUyznr$&WLi!NMMl(Zzda9u&k zhQg9HMcFIUb5>?lt;k+@tnL5*|3Ir~0Th3-FfuSGG3bB{2lH@y}y)oJi*?vas^;AHx0!NJ4O*0k`o2+t&DrWyu5 zj(R_4EAZWzvu{-`eq28PUe%&URf`_h zE_+tJ_)+7$GZ*iFyZ`d%)3?7~fBgOG!>{*W{=EDA+cayJS@s^YtlehWyG=89nr7}W zN#A0YwaYYPyJ_Zj(~NDV8Cy-#Hk+hvGEUuKoU-01d5uxhYQuyThKVc8vv--KZZu9= zXPms&C~=ih(ki3Gm4*q+4HK4G9!EP7DB;9l;e)3wW=S1);7wfIrh;)iuBUev7savxPMept2WLBpz- zl{1dl%syE&>qOV`OMR=aPTg>8*4Df8cRgIP@5#zT&(|J%z4qv<&8OaOJM(VGx%ay- zyx(`}!=bC6_Fi~%{N|U#SKnWJ@cqL5ZxaB{6^aMg)EY~kcr0o;F`@iBAmmD4aw zcJ%}n9z5H~%p;Lf_({d9QHiauL$Pq7?^La@XLgPYmUIX*IUU;Y;DB1=lJg6knHiIv z4oIoD>#dAiD>aedUN>b$V8Mw=Z*OlCe4AmQ>^e(#W#oFdmc!gVTB-#j*i)88R+va- zt5tEnUtgY3YOGs)!)un)iLc+Kv)!q+->kmkw%6{`)a%dS^4wRRMO~atW}{Y_(TkYO zinQITvfHS(-K@CXuW-0oZnaowt5a{aS$V-^YpPgms#iAIfI}-V YXj#K1%YuiE@F+lFfI!BNg8~5nJ9%&rhyVZp literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/expandblock.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/expandblock.gif new file mode 100644 index 0000000000000000000000000000000000000000..52e1f3cfa89b7367ed701fb1bf6801223104e55c GIT binary patch literal 222 zcmZ?wbhEHb6krfwIKsfd=vLJ|ZS~PpSC5{)diC@FS3m#1`St(Z@Bbfv|Ns2w|F^&Y zfByad>;L~h|Nj5^|Nrm*|Ns7j00YrL@h1x-1A`!g4oC;cP6pQK1eLy&%z0@G*5w4S zx#*nMI+^-hqL!;zegd#}b211Gj{R6(SP~4v82hCR$eriX|i_XDJ(;JJcQ^ j!1#lsZ$~qOVA9KO|w{Y*X zDL9zic&4;+Z(i%!%C?g=eV3X$Pt^CFX_<1l>*$LE0W%$|j%C*!&+j^4+jg?5=XB%L zD_zsCc2B#~J^5_M+#8b?+?=}L=Cq}^XRp7%Z2N=td!KAO_wwYOk2iv+xrWW~NL?LT zxGz3_UO?8G=$gaX(=K z<4o6udAF7>nv|+~Swi##Ix87b+vn!=`Pg?1k z#PW5?RqIo0Hm28Y%A9_@b;-FY%Pve?c7EFGD|5EoTDJM-((Sia?Y^_VVRKf?w!G%8 zx$Qd&+jkTI0}~vm#1e`>Sr{1@3K(=i8bEQvz`nAfpsBf~wXGm0J0Y&UIWO7IT*J}Z zy*)q6LS04KNX$pCH9u2RQ9(prR!GfDyCum?NmQ7TQNTdb#IiZpK~+YOf2)2_fK510 zQ=+My^w#r?P9DCTP4UK3d>6Jl1*fq$#o8K5@ao)Qc*(%p>}=<%8}uSDotvTA+SC51 zlZ!t?Mi0jha}Rb=-N+p|2N(4zD2j@&nV=cWdVs-^k5khjp`qb$PrDQs7YlOV literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/fanrouter.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/fanrouter.gif new file mode 100644 index 0000000000000000000000000000000000000000..b260a446f4189bc0498fbde549b9d9c5de6ae3df GIT binary patch literal 224 zcmZ?wbhEHb6krfwIKsg2pMgQG!NamW$azw9;MAy)Y0=T$89tMfgQus5Ps@&+njbbJ zD|9vxWk=7;k8LeZm{L+WxjJq^VbYT1?3L-+OS5to=H{%-s9KR-xUQgNLt$x6=fY!c zJNF)D00YIJESw;Q4u}BR$-oiIz-S?%z}RrGnSA}gGu~I3T#cHQ!G59$tXaqa9wDE_stuT1lYXDaDOc($F literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hideall.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hideall.gif new file mode 100644 index 0000000000000000000000000000000000000000..74c64f093ff0147b9b3f8e51e2f15bb029f683d1 GIT binary patch literal 161 zcmZ?wbhEHb6krfw*v!DtJ#F>UjfWZCs($|cfA#bKkH7!F`St(Z@Bg3w{Qvg%|F8f5 z|NI951{hHM$->CMz{8*e5&)UOz~VRIr043rW-rb<^SWqs$+0qJtYW%sxo^!AsV`2K z4Ij(>+4P}(&T5A*g4|9_4GI}ntQ}8`K5}_-Hm}jQ7GRs!wLyQ)3D0T!N^YHr*lGBI HgTWdA!W2o- literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hideconst.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hideconst.gif new file mode 100644 index 0000000000000000000000000000000000000000..23f1bf70ebe959372a9dd2630e8227af1847c16e GIT binary patch literal 216 zcmZ?wbhEHb6krfwIKsfd=vLJ|ZS~PpSC5{)diC@FS3m#1`St(Z@Bbfv|Ns2w|F^&Y zfByad>;L~h|Nj5^|Nrm*|Ns7j00YrL@h1x-1A`!g4oC;cP6pP{1eLy&%z0@G*5w4S zx#*nMP?D$2b7y68X4yWI5cqvpW(W3uzSM`Ch7JS{RcFd<^2y{ eJ6I;8z|eH<8W#^kvt^dZ{ps`S>H`E78LR>O`&ZQf literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hideoperation.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hideoperation.gif new file mode 100644 index 0000000000000000000000000000000000000000..6eeb96fd45d8b5890e682ae68bc6dc436c4fc187 GIT binary patch literal 221 zcmZ?wbhEHb6krfwIKsfd=vLJ|ZS~PpSC5{)diC@FS3m#1`St(Z@Bbfv|Ns2w|F^&Y zfByad>;L~h|Nj5^|Nrm*|Ns7j00YrL@h1x-1A`!g4oC;cP6pPf1eLy&%z0@G*5w4S zx#*nM literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hideparam.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hideparam.gif new file mode 100644 index 0000000000000000000000000000000000000000..a56ee70235e1b67740f29977e815c7ca4938bc47 GIT binary patch literal 217 zcmZ?wbhEHb6krfwIKsfd=vLJ|ZS~PpSC5{)diC@FS3m#1`St(Z@Bbfv|Ns2w|F^&Y zfByad>;L~h|Nj5^|Nrm*|Ns7j00YrL@h1x-1A`!g4oC;cP6pPn1eLy&%z0@G*5w4S zx#*nMP?D$2b7y68X4yWI5cqvpW(W3uzSM`Ch0)wMn^U~1{Oz_ f2g(-r`j}bPriw>5I!c&2efF-auVqnDWUvMR{4`YK literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hidephi.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/hidephi.gif new file mode 100644 index 0000000000000000000000000000000000000000..2bda720fe9a4bca963605eeaddf11a1edd83d19f GIT binary patch literal 217 zcmZ?wbhEHb6krfwIKsfd=vLJ|ZS~PpSC5{)diC@FS3m#1`St(Z@Bbfv|Ns2w|F^&Y zfByad>;L~h|Nj5^|Nrm*|Ns7j00YrL@h1x-1A`!g4oC;cP6pPn1eLy&%z0@G*5w4S zx#*nMiw-!)1XwZ$Y2csilYb5A3b{X@ZrOI_wL=jd-u+rJGXD&zIpTJ zjT<+vUAuPm>eVY(u3Wx+`O>9J7cX8sfByWrbLY;UJ$vTNnbW6FpE`Bw4@_w3oTYuB!wJ9lp1zJ2S~ty{Ki z*}Qr4rcIkRZrr$G!-h3$)~s5!YQ>5b%a<=-wrttbrArqsTsUvuyg75`%$_}a%9JS+ zCQRt>@9*yJ?&#=fZEbC8YHDa`sI9H7s;Vk2EiEZ2DK0K9EG#T2D9F#x&&$ip$;rve z%F4{l%*e<{N=owg_XmayID}|ODE?&OWME)q&;i*2iW3HoOAO2$G7<|8vo~>YPU}!` zba!eLcI)7naOe=LG-DA%prgRyCLW!Xf)5uKw|BCeb8I{Cz|pyFqLjl755d+1R({oh zD+W$aj&ezuCvf~JSag(4$fS#d<kwNO}tpP7jP0DD{_ AiU0rr literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/layoutinvisible.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/layoutinvisible.gif new file mode 100644 index 0000000000000000000000000000000000000000..f08f10dac9d654c87572180c4de40e5f85e15044 GIT binary patch literal 322 zcmZ?wbhEHb6krfwxXJ(m|Ns9lC~wU#YdL!2+_f8bimTesUA&swRGHRWmDp99G@(qb z!NamW$Y*l0^Q7p|*;%2pv%_X&MbFC*o}M1JpfG4kOxWc3z^PFo)1nJk6(uc6E?ieo zvZ1hKO;PsB^qiF$RV%U=9%}>I0=A9HgyK&YMg|6D1|5*OAU`p%#UGeh;GrYc@A2?S zpeLWQtRVX%8ACg1fyJ+OwCMC{F`Z z&JLTA6+JIMczSx=g2KYd)zRGmVrU>CkrD3gB*hn$R?1T46F_d)caC0=Ph6Cpm8$g?$#|y3nN1vt2vK)wj6xd sn8CqvXl{Ce&$}5OPlFO7H2Ejq=m|RzVP+N8Z|tej$-7cYl!?I_0PZMFcmMzZ literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/options.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/options.gif new file mode 100644 index 0000000000000000000000000000000000000000..f176369975038ee98ba6a68dc8d981a2952c8080 GIT binary patch literal 564 zcmb`E+fPyf0LD*c*~NnYzFlTtc|!9i|9aS3W} zN#^3AC5SLA#^JU_u`xED*cxN;##~zu_3{P%4SoCVd;A`LUni%dxkbbxSi}#a9F#J~ z#w#w~2;dZALIkG>lVVJX@&4g2r{w44BYc!%S_=0N+%n9_;C%|O9Dz~HjpCCIpAGmj zfrJx@xA^)F-%3nDS`FzA4AtUEi8>6|L*4*I(;wvtyx_pv0~;3(9vr=R%`d(b!ZnBw zk1;KSdl;S(%#iSsm{nj-i{NvFEMTl)Y>3zq8%KP+SS%I_g?v7r%jL4!Y$lUQr_-ra zDw#|s5{Y;`9*f1I(P$(R35UZB!-PViU@#a61pI!#&*$@aJT8~Z;c(dPcAL#+wOY+) zv&m#K8jX6rUZ>M(wOWlvqgJbFnpUY)N~KbvP*4;_l4MEPKXd<~YJXQOAP5Q3Sl%js zLR5~8NS=|@P2MevCV6dV_-dNh&c5DfpnBUn1d=<#dlvrLOSkXdIM;9KJKuG!=Sueg zy?lj$)5*Db`Kp*+wvJVGfh+1Cct9^*yS@6r{`;27HQP3AIK5=IVe#s%8xJ09ZQP}< zSheN&$|EQD)fpCTUf*=MW$#YQo>NB;H8<2)Bnzv)&&qQQyS=`Pnjf6+t|40g0v!Db Ar~m)} literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/showall.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/showall.gif new file mode 100644 index 0000000000000000000000000000000000000000..0205b29176d4e60307639b6ac80ebfc40be61c3b GIT binary patch literal 164 zcmZ?wbhEHb6krfw*v!DtJ#F>UjfWZCs($|cfA#bKkH7!F`St(Z@Bg3w{Qvg%|F8f5 z|NI951{hHM$->CMz{8*e5&)UOz!Es&r043rW-rb<^SWqs$+0qJtYW%sxo^!AsVxqd z4Ij(xarn?aXSKr@L2f6e289eO){ZAef}c8;L~h|Nj5^|Nrm*|Ns7j00YrL@h1x-1A`!g4oC;cP6pQS1eLy&%z0@G*5w4S zx#*nMjwO1X25t!rD?}y~91<~%O|-5M6ibMGZ17i5BIB;I g)(5M5eatS`w{B%(KCqxq>g6xhz=p;kK}7~@0FYo<^8f$< literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/showoperation.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/showoperation.gif new file mode 100644 index 0000000000000000000000000000000000000000..ce829e37b5cba5ab56c6be584d7d4763f9a519ab GIT binary patch literal 224 zcmZ?wbhEHb6krfwIKsfd=vLJ|ZS~PpSC5{)diC@FS3m#1`St(Z@Bbfv|Ns2w|F^&Y zfByad>;L~h|Nj5^|Nrm*|Ns7j00YrL@h1x-1A`!g4oC;cP6pQ41eLy&%z0@G*5w4S zx#*nMI+^-hqL!;zegd#}b211Gj{R6(SP~4v82hCR$eriX|j2R@^Emk@3)3 lql2M_rTva$1IO<7$=U`CJRdZaos=pYoAVl*0+kdQtN|pGS4;o^ literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/showparam.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/showparam.gif new file mode 100644 index 0000000000000000000000000000000000000000..bb6de210779e085ff73b5d3a738817bf586c5b12 GIT binary patch literal 219 zcmZ?wbhEHb6krfwIKsfd=vLJ|ZS~PpSC5{)diC@FS3m#1`St(Z@Bbfv|Ns2w|F^&Y zfByad>;L~h|Nj5^|Nrm*|Ns7j00YrL@h1x-1A`!g4oC;cP6pP91eLy&%z0@G*5w4S zx#*nMI+^-hqL!;zegd#}b211Gj{R6(SP~4v82hCR$eriX|i_XDJ(;JJcQ^ g!1#lsZ$~qOVA;L~h|Nj5^|Nrm*|Ns7j00YrL@h1x-1A`!g4oC;cP6pP91eLy&%z0@G*5w4S zx#*nMI+^-hqL!;zegd#}b211Gj{R6(SP~4v82hCR$eriX|jIR@^Emk@4{H hMTQF50(QnreYc_-86D&c7rUj_tJF0qH7YV#0|1WoR<8g6 literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/zoomin.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/zoomin.gif new file mode 100644 index 0000000000000000000000000000000000000000..a2aaaeb008ec0146d7faf0898a13271526724ffc GIT binary patch literal 923 zcmZ?wbhEHb6krfw_|IU-NIWy_Z%M~z+{#_J{uzw&jJQUHpf;mn->cXTwEe2s#eftl67>Y|5~r5tF|UJ QvhnWHa`~XI00~BG08ubc%K!iX literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/zoomout.gif b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/icons/zoomout.gif new file mode 100644 index 0000000000000000000000000000000000000000..6dc4cbfa0955a58b990078cea3737802df72f703 GIT binary patch literal 924 zcmZ?wbhEHb6krfw_|IU-NIWy_Z%M~Y(+}Mqz4SlJS}{>6#<7Bmw4G~`Ft>0d8I34 YVcguP((I$myiygR9S`CiAR%ZC0IwEPJpcdz literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/layer.xml b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/layer.xml new file mode 100644 index 000000000000..f3ef00571f01 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowEditor/src/main/resources/at/ssw/visualizer/dataflow/layer.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/DataFlowGraph/pom.xml b/visualizer/C1Visualizer/DataFlowGraph/pom.xml new file mode 100644 index 000000000000..d6c3cbe6654c --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/pom.xml @@ -0,0 +1,84 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + DataFlowGraph + 1.13-SNAPSHOT + nbm + DataFlowGraph + + UTF-8 + + + + at.ssw.visualizer + GraphLayoutImpl + ${project.version} + + + at.ssw.visualizer + GraphLayoutAPI + ${project.version} + + + at.ssw.visualizer + GraphHelper + ${project.version} + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + org.netbeans.api + org-netbeans-api-visual + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.dataflow.attributes + at.ssw.visualizer.dataflow.graph + at.ssw.visualizer.dataflow.instructions + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphCluster.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphCluster.java new file mode 100644 index 000000000000..059b84bdf9ff --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphCluster.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.positionmanager.impl; + +import at.ssw.positionmanager.Cluster; +import java.util.Set; +import java.util.TreeSet; + +/** + * Implements a cluster in the open layouter graph model of SSW. + * + * @author Stefan Loidl + */ +public class GraphCluster implements Cluster, Comparable{ + + Set successors; + Set predecessors; + + private int index; + + /** Creates a new instance of GraphCluster */ + public GraphCluster(int index, Set successors, Set predecessors) { + this.index=index; + if(successors!=null){ + this.successors=successors; + } + else this.successors=new TreeSet(); + + if(predecessors!=null){ + this.predecessors=predecessors; + } + else this.predecessors=new TreeSet(); + } + + //Never used here! + public Cluster getOuter() { + return null; + } + + public Set getSuccessors() { + return successors; + } + + public Set getPredecessors() { + return predecessors; + } + + + public int compareTo(Cluster o) { + if(o instanceof GraphCluster){ + return index-((GraphCluster)o).index; + } + return this.hashCode()-o.hashCode(); + } +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphLink.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphLink.java new file mode 100644 index 000000000000..07ac01edea00 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphLink.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.positionmanager.impl; + +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Port; +import java.awt.Point; +import java.util.LinkedList; +import java.util.List; + +/** + * Implements a Link in the open Layouter specification of SSW. + * + * @author Stefan Loidl + */ +public class GraphLink implements Link, Comparable{ + + private Port from, to; + List controlPoints; + protected int index; + + /** Creates a new instance of GraphLink */ + public GraphLink(int index ,Port from, Port to) { + this.index=index; + this.from=from; + this.to=to; + controlPoints=new LinkedList(); + } + + public Port getFrom() { + return from; + } + + public Port getTo() { + return to; + } + + public List getControlPoints() { + return controlPoints; + } + + public void setControlPoints(List list) { + controlPoints=list; + } + + + public int compareTo(Link o) { + if(o instanceof GraphLink){ + return index-((GraphLink)o).index; + } + return this.hashCode()-o.hashCode(); + } +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphPort.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphPort.java new file mode 100644 index 000000000000..e54b28936bff --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphPort.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.positionmanager.impl; + +import at.ssw.positionmanager.LayoutGraph; +import at.ssw.positionmanager.Port; +import at.ssw.positionmanager.Vertex; +import java.awt.Dimension; +import java.awt.Point; +import java.util.Set; + +/** + * Implements a Port in the open layouter model of SSW. + * + * @author Stefan Loidl + */ +public class GraphPort implements Port{ + + private Vertex vertex; + private LayoutGraph graph=null; + + /** Creates a new instance of GraphPort */ + public GraphPort(Vertex v) { + this.vertex=v; + } + + public Vertex getVertex() { + return vertex; + } + + public void setLayoutGraph(LayoutGraph graph){ + this.graph=graph; + } + + public Point getRelativePosition() { + Dimension d=vertex.getSize(); + int y=0, x=0; + + if(graph==null) return new Point(d.width/2,d.height/2); + + Set ports=graph.getInputPorts(vertex); + if(!ports.contains(this)){ + ports=graph.getOutputPorts(vertex); + y=d.height; + } + + int offset= d.width/(ports.size()+1); + boolean found=false; + for(Port p:ports){ + x+=offset; + if(p==this) { + found=true; + break; + } + } + if(found) return new Point(x,y); + else return new Point(d.width/2,d.height/2); + } +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphVertex.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphVertex.java new file mode 100644 index 000000000000..affa443eaf7a --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/positionmanager/impl/GraphVertex.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.positionmanager.impl; + +import at.ssw.positionmanager.Cluster; +import at.ssw.positionmanager.Vertex; +import at.ssw.visualizer.dataflow.graph.InstructionNodeWidget; +import java.awt.Dimension; +import java.awt.Point; + +/** + * Implements a Vertex which is used as node representation in the open layouter definition of + * SSW. + * + * @author Stefan Loidl + */ +public class GraphVertex implements Vertex { + + private Cluster cluster=null; + private Point position=null; + private boolean dirty=false; + private boolean fixed=false; + private boolean marked=false; + + private InstructionNodeWidget widget; + protected int index; + + /** Creates a new instance of GraphVertex */ + public GraphVertex(int index, Cluster cluster, Point position, InstructionNodeWidget widget) { + assert widget !=null; + this.index=index; + this.cluster=cluster; + this.position=position; + this.widget=widget; + } + + public Cluster getCluster() { + return cluster; + } + + public Dimension getSize() { + return widget.getBounds().getSize(); + } + + public Point getPosition() { + Point p=(Point)position.clone(); + p.translate(-InstructionNodeWidget.BORDERINSET,-InstructionNodeWidget.BORDERINSET); + return p; + } + + public void setPosition(Point p) { + position=p; + } + + public boolean isDirty() { + return dirty; + } + + public void setDirty(boolean d){ + dirty=d; + } + + public boolean isRoot() { + return false; + } + + public int compareTo(Vertex o) { + if(o instanceof GraphVertex){ + return index-((GraphVertex)o).index; + } + return this.hashCode()-o.hashCode(); + } + + public boolean isExpanded() { + return widget.isExpanded(); + } + + public boolean isFixed() { + return fixed; + } + + public boolean isMarked() { + return marked; + } + + public void setFixed(boolean b){ + fixed=b; + } + + public void setMarked(boolean b){ + marked=b; + } + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/ExpandNodeSwitchAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/ExpandNodeSwitchAttribute.java new file mode 100644 index 000000000000..062da3a8b82d --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/ExpandNodeSwitchAttribute.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +import at.ssw.visualizer.dataflow.graph.InstructionNodeGraphScene; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JMenuItem; + +/** + * + * @author Stefan Loidl + */ +public class ExpandNodeSwitchAttribute implements ISwitchAttribute, IPopupContributorAttribute, IExpandNodeAttribute, IPathHighlightAttribute{ + + private boolean value, remove; + private String description; + private JMenuItem mitem; + private InstructionNodeGraphScene scene; + + /** Creates a new instance of ExpandInfluenceSwitchAttribute */ + public ExpandNodeSwitchAttribute(boolean initialValue, String desc, boolean remove, InstructionNodeGraphScene scene) { + value=initialValue; + description=desc; + this.remove=remove; + mitem=new JMenuItem(description); + mitem.addActionListener(this); + this.scene=scene; + } + + public void setSwitch(boolean s) { + value=s; + } + + public boolean getSwitch() { + return value; + } + + public String getSwitchString() { + return description; + } + + public boolean validate() { + return value; + } + + public boolean removeable() { + return remove; + } + + public JMenuItem getMenuItem() { + return mitem; + } + + public void actionPerformed(ActionEvent e) { + value=!value; + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + + } + + public boolean showBlock() { + return false; + } + + public boolean showInstruction() { + return true; + } +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/ExpandStructureAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/ExpandStructureAttribute.java new file mode 100644 index 000000000000..3f8323ebe241 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/ExpandStructureAttribute.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +import at.ssw.visualizer.dataflow.graph.InstructionNodeGraphScene; +import java.awt.event.ActionEvent; +import javax.swing.JMenuItem; + +/** + * An ExpandStructureAttribute is an IExpandNodeAttribute that is depending on + * a ISwitchAttribute and is contribuing a Menuitem to a Popup Menu implementing + * the IPopupContributer interface. + * + * @author Stefan Loidl + */ +public class ExpandStructureAttribute implements IExpandNodeAttribute, IPopupContributorAttribute, IPathHighlightAttribute{ + + private ISwitchAttribute parentAttribute; + private JMenuItem mitem; + private InstructionNodeGraphScene scene; + + /** Creates a new instance of ExpandStructureAttribute */ + public ExpandStructureAttribute(ISwitchAttribute parent, InstructionNodeGraphScene scene) { + parentAttribute=parent; + mitem=new JMenuItem(parent.getSwitchString()); + mitem.addActionListener(this); + this.scene=scene; + } + + public boolean showBlock() { + return false; + } + + public boolean showInstruction() { + return true; + } + + public boolean validate() { + return parentAttribute.getSwitch(); + } + + public boolean removeable() { + return true; + } + + public JMenuItem getMenuItem() { + return mitem; + } + + public void actionPerformed(ActionEvent e) { + parentAttribute.setSwitch(false); + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + } + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IAdditionalWidgetAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IAdditionalWidgetAttribute.java new file mode 100644 index 000000000000..95c84e66ca9d --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IAdditionalWidgetAttribute.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +import org.netbeans.api.visual.widget.Widget; + +/** + * + * @author Stefan Loidl + */ +public interface IAdditionalWidgetAttribute extends INodeAttribute{ + + public Widget getWidget(); +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IExpandNodeAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IExpandNodeAttribute.java new file mode 100644 index 000000000000..9fff166f6289 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IExpandNodeAttribute.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +/** + * + * @author Stefan Loidl + */ +public interface IExpandNodeAttribute extends INodeAttribute { + public boolean showBlock(); + public boolean showInstruction(); +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IInvisibilityAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IInvisibilityAttribute.java new file mode 100644 index 000000000000..77ea8a12a235 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IInvisibilityAttribute.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +/** + * This interface is for reasons of grouping only. + * + * @author Stefan Loidl + */ +public interface IInvisibilityAttribute extends INodeAttribute{} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/INodeAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/INodeAttribute.java new file mode 100644 index 000000000000..975a52fe808b --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/INodeAttribute.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +/** + * This is the interface for attributes that can be applied + * to a node. Such attributes could be: visibility or + * full information... + * + * @author Stefan Loidl + */ +public interface INodeAttribute { + + /** + * This methode is meant to return if the attribute + * is active (from its own point of view). If this + * method returns false and removable return true the + * attribute can savely be removed from the attribute list. + */ + public boolean validate(); + + /** + * Returns if the attribute can be removed from the attributelist + * after validate returns true. + * Some attributes are chained to nodes for lifetime. In this + * case removable always returns false. + */ + public boolean removeable(); +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IPathHighlightAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IPathHighlightAttribute.java new file mode 100644 index 000000000000..b3dcffcd9908 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IPathHighlightAttribute.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +/** + * Nodes implementing this interface are members of a highlighted + * path. + * + * @author Stefan Loidl + */ +public interface IPathHighlightAttribute extends INodeAttribute{ + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IPopupContributorAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IPopupContributorAttribute.java new file mode 100644 index 000000000000..7d3b6fed9198 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/IPopupContributorAttribute.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +import java.awt.event.ActionListener; +import javax.swing.JMenuItem; + +/** + * + * @author Stefan Loidl + */ +public interface IPopupContributorAttribute extends INodeAttribute, ActionListener{ + public JMenuItem getMenuItem(); + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/ISwitchAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/ISwitchAttribute.java new file mode 100644 index 000000000000..ed78b729379d --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/ISwitchAttribute.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +/** + * + * @author Stefan Loidl + */ +public interface ISwitchAttribute extends INodeAttribute{ + + public void setSwitch(boolean s); + + public boolean getSwitch(); + + /** + * Returns a string as description for the switch. + */ + public String getSwitchString(); +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/InvisibleAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/InvisibleAttribute.java new file mode 100644 index 000000000000..0ee8e5d89bfa --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/InvisibleAttribute.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +/** + * + * @author Stefan Loidl + */ +public class InvisibleAttribute implements IInvisibilityAttribute{ + + public boolean validate() { + return true; + } + + public boolean removeable() { + return true; + } +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/InvisibleNeighbourAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/InvisibleNeighbourAttribute.java new file mode 100644 index 000000000000..ab7ed756b48c --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/InvisibleNeighbourAttribute.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +import at.ssw.visualizer.dataflow.graph.InstructionNodeWidget; +import javax.swing.JToggleButton; +import org.netbeans.api.visual.widget.Widget; + +/** + * + * @author Stefan Loidl + */ +public class InvisibleNeighbourAttribute implements IAdditionalWidgetAttribute{ + + private Widget widget; + private InstructionNodeWidget neighbour; + private boolean removeable; + + /** Creates a new instance of TBAdditionalWidgetAttribute */ + public InvisibleNeighbourAttribute(Widget w, InstructionNodeWidget n, boolean removeable) { + widget=w; + neighbour=n; + this.removeable=removeable; + } + + public Widget getWidget() { + return widget; + } + + public boolean validate() { + return !neighbour.isWidgetVisible(); + } + + public boolean removeable() { + return removeable; + } + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/SelfSwitchingExpandAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/SelfSwitchingExpandAttribute.java new file mode 100644 index 000000000000..6a42d0f1e26f --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/SelfSwitchingExpandAttribute.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +import at.ssw.visualizer.dataflow.graph.InstructionNodeGraphScene; +import java.awt.event.ActionEvent; +import javax.swing.JMenuItem; + +/** + * Expand Attribute that contributes a menuitem to the popupmenu which enables the user to + * deactivate the attribute itself. + * + * @author Stefan Loidl + */ +public class SelfSwitchingExpandAttribute implements IPopupContributorAttribute, IExpandNodeAttribute, IPathHighlightAttribute{ + + private boolean state; + private JMenuItem mitem; + private InstructionNodeGraphScene scene; + + /** Creates a new instance of SelfSwitchingExpandAttribute */ + public SelfSwitchingExpandAttribute(String description, InstructionNodeGraphScene scene) { + state=true; + mitem=new JMenuItem(description); + mitem.addActionListener(this); + this.scene=scene; + } + + public JMenuItem getMenuItem() { + return mitem; + } + + public boolean validate() { + return state; + } + + public boolean removeable() { + return true; + } + + public void actionPerformed(ActionEvent e) { + state=false; + scene.refreshAll(); + scene.validate(); + scene.autoLayout(); + } + + public boolean showBlock() { + return false; + } + + public boolean showInstruction() { + return true; + } + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/TBExpandAllAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/TBExpandAllAttribute.java new file mode 100644 index 000000000000..a7dab0547b4d --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/TBExpandAllAttribute.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +import javax.swing.JToggleButton; + +/** + * + * @author Stefan Loidl + */ +public class TBExpandAllAttribute implements IExpandNodeAttribute{ + + private JToggleButton button; + + /** Creates a new instance of TBExpandAllAttribute */ + public TBExpandAllAttribute(JToggleButton tb) { + button=tb; + } + + public boolean showBlock() { + return false; + } + + public boolean showInstruction() { + return true; + } + + public boolean validate() { + return button.isSelected(); + } + + public boolean removeable() { + return false; + } + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/TBInvisibilityAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/TBInvisibilityAttribute.java new file mode 100644 index 000000000000..6c3a9acf8e57 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/TBInvisibilityAttribute.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +import javax.swing.JToggleButton; + +/** + * + * @author Stefan Loidl + */ +public class TBInvisibilityAttribute implements IInvisibilityAttribute{ + + private JToggleButton button; + + /** Creates a new instance of TBVisibilityAttribute */ + public TBInvisibilityAttribute(JToggleButton tb) { + button=tb; + } + + public boolean validate() { + return button.isSelected(); + } + + public boolean removeable() { + return false; + } + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/TBShowBlockAttribute.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/TBShowBlockAttribute.java new file mode 100644 index 000000000000..4148a5c2e87a --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/attributes/TBShowBlockAttribute.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.attributes; + +import javax.swing.JToggleButton; + +/** + * + * @author Stefan Loidl + */ +public class TBShowBlockAttribute implements IExpandNodeAttribute{ + + private JToggleButton button; + + /** Creates a new instance of TBShowBlockAttribute */ + public TBShowBlockAttribute(JToggleButton tb) { + button=tb; + } + + public boolean showBlock() { + return true; + } + + public boolean showInstruction() { + return false; + } + + public boolean validate() { + return button.isSelected(); + } + + public boolean removeable() { + return false; + } + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/ClusterWidget.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/ClusterWidget.java new file mode 100644 index 000000000000..c2b9e934c344 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/ClusterWidget.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.graph; + +import java.awt.Color; +import java.awt.Point; +import java.awt.Rectangle; +import java.util.Collection; +import org.netbeans.api.visual.action.ActionFactory; +import org.netbeans.api.visual.action.EditProvider; +import org.netbeans.api.visual.action.WidgetAction; +import org.netbeans.api.visual.border.Border; +import org.netbeans.api.visual.border.BorderFactory; +import org.netbeans.api.visual.widget.LabelWidget; +import org.netbeans.api.visual.widget.Scene; +import org.netbeans.api.visual.widget.Widget; + +/** + * + * @author Stefan Loidl + */ +public class ClusterWidget extends Widget{ + + private Collection widgets; + private String id; + + private Border VISIBLEBORDER; + private static Border INVISIBLEBORDER=BorderFactory.createEmptyBorder(); + + private static final int DASHSIZE=10; + + + private static final int PADDING=10; + //This value has to be applied to the + //position because of an error in border framework + private static final int CORRECT=5; + + private boolean visible=true; + + private LabelWidget label; + + + /** Creates a new instance of ClusterWidget */ + public ClusterWidget(String id, Collection widgets, Scene scene, Color col) { + super(scene); + this.widgets=widgets; + this.id=id; + VISIBLEBORDER=BorderFactory.createDashedBorder(col,DASHSIZE,DASHSIZE/2,true); + + label=new LabelWidget(scene,id); + label.setForeground(col); + addChild(label); + + //If edit suppored by scene + if(scene instanceof EditProvider){ + WidgetAction.Chain actions = getActions (); + actions.addAction(ActionFactory.createEditAction((EditProvider)scene)); + } + } + + /** Returns the id of the cluster */ + public String getId(){ + return id; + } + + /** Returns the clustered widgets */ + public Collection getWidgets(){ + return widgets; + } + + /** Sets the border of the widget to modify its visibility*/ + private void setWidgetVisible(boolean v){ + removeChildren(); + if(v) { + this.setBorder(VISIBLEBORDER); + addChild(label); + } + else this.setBorder(INVISIBLEBORDER); + } + + /** Defines if border is shown*/ + public void setVisibility(boolean b){ + visible=b; + } + + /** Refreshes the Compoments- size and position*/ + public void refresh(){ + if(widgets!=null){ + int minX=Integer.MAX_VALUE; + int minY=Integer.MAX_VALUE; + int maxX=Integer.MIN_VALUE; + int maxY=Integer.MIN_VALUE; + boolean changed=false; + + for(Widget w:widgets){ + //discard invisible widgets + if(w instanceof InstructionNodeWidget){ + if(!((InstructionNodeWidget)w).isWidgetVisible()) continue; + } + + Point p=w.getPreferredLocation(); + Rectangle r=w.getBounds(); + + if(w!=null && r!=null){ + if(minX > p.x) minX=p.x; + if(minY > p.y) minY=p.y; + if(maxX < p.x+r.width) maxX=p.x+r.width; + if(maxY < p.y+r.height) maxY=p.y+r.height; + changed=true; + } + } + + if(changed && visible){ + setPreferredBounds(new Rectangle(maxX-minX+2*PADDING,maxY-minY+2*PADDING)); + setPreferredLocation(new Point(minX-PADDING-CORRECT,minY-PADDING-CORRECT)); + setWidgetVisible(true); + revalidate(); + } + else setWidgetVisible(false); + } + } + + + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/DirectLineRouter.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/DirectLineRouter.java new file mode 100644 index 000000000000..aa717d4c3e9c --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/DirectLineRouter.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.graph; + +import at.ssw.positionmanager.LayoutGraph; +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Port; +import at.ssw.positionmanager.Vertex; +import at.ssw.positionmanager.impl.GraphVertex; +import java.awt.Point; +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.List; +import org.netbeans.api.visual.router.Router; +import org.netbeans.api.visual.widget.ConnectionWidget; +import org.netbeans.api.visual.widget.Widget; + +/** + * + * @author Stefan Loidl + */ +public class DirectLineRouter implements Router{ + + private InstructionNodeGraphScene scene; + + /** Creates a new instance of DirectLineRouter */ + public DirectLineRouter(InstructionNodeGraphScene scene) { + this.scene=scene; + } + + public List routeConnection(ConnectionWidget widget) { + List cp=new LinkedList(); + + LayoutGraph graph=scene.getLayoutGraph(); + Hashtable WidgetToVertex=scene.getWidgetToVertex(); + if(graph==null || WidgetToVertex==null) return cp; + + Vertex v1=WidgetToVertex.get(widget.getSourceAnchor().getRelatedWidget()); + Vertex v2=WidgetToVertex.get(widget.getTargetAnchor().getRelatedWidget()); + + if(v1==null || v2==null) return cp; + + //do the routing + scene.getExternalLayouter().doRouting(graph); + + //Reset dirty state + for(Vertex v: graph.getVertices()){ + if(v instanceof GraphVertex) ((GraphVertex)v).setDirty(false); + } + + for(Port p: graph.getOutputPorts(v1)){ + for(Link l: graph.getPortLinks(p)){ + if(l.getTo().getVertex()==v2){ + cp=l.getControlPoints(); + } + } + } + + + return cp; + } + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/HiddenNodesWidget.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/HiddenNodesWidget.java new file mode 100644 index 000000000000..483fd0c94e2f --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/HiddenNodesWidget.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.graph; + +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.Shape; +import javax.swing.JPopupMenu; +import org.netbeans.api.visual.action.ActionFactory; +import org.netbeans.api.visual.action.PopupMenuProvider; +import org.netbeans.api.visual.widget.Scene; +import org.netbeans.api.visual.widget.Widget; + +/** + * + * @author Stefan Loidl + */ +public class HiddenNodesWidget extends Widget{ + + //true if arrow points upward + private boolean upward; + private final int ARROWHIGHT=4; + + /** + * Creates a hidden-nodes widget. + * @param up is true if arrow shall point upwards. + */ + public HiddenNodesWidget (Scene scene, boolean up) { + super (scene); + upward=up; + } + + /** + * Calculates a client area of the widget. (The width is 0, as + * it should be defined by parent widget width) + * @return the calculated client area + */ + protected Rectangle calculateClientArea () { + return new Rectangle (0, 0, 0, ARROWHIGHT); + } + + /** + * Paints the separator widget. + */ + protected void paintWidget() { + Graphics2D gr = getGraphics(); + gr.setColor (getForeground()); + Rectangle bounds = getBounds (); + Insets insets = getBorder ().getInsets (); + int x[]=new int[4]; + int y[]=new int[4]; + + x[0]=0; + x[1]=bounds.width -insets.left -insets.right; + x[2]=(bounds.width - insets.left - insets.right)/2; + x[3]=0; + + if(upward){ + y[0]=ARROWHIGHT; + y[1]=ARROWHIGHT; + y[2]=0; + y[3]=ARROWHIGHT; + } + else{ + y[0]=0; + y[1]=0; + y[2]=ARROWHIGHT; + y[3]=0; + } + + Polygon p=new Polygon(x,y,4); + + gr.drawPolygon(p); + gr.fill(p); + } + + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InscribeNodeWidget.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InscribeNodeWidget.java new file mode 100644 index 000000000000..4d3b8541e962 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InscribeNodeWidget.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.graph; + +import at.ssw.visualizer.dataflow.instructions.Instruction; +import java.awt.Color; +import java.awt.Font; +import org.netbeans.api.visual.border.BorderFactory; +import org.netbeans.api.visual.widget.LabelWidget; + +/** + * + * @author Stefan Loidl + */ +public class InscribeNodeWidget extends LabelWidget{ + + private final static int LEFTINSET=2; + private final static int TOPINSET=1; + + + /** Creates a new instance of InscribeNodeWidget */ + public InscribeNodeWidget(Instruction i, InstructionNodeGraphScene s) { + super(s); + + if(i.getInstructionType()==Instruction.InstructionType.PARAMETER) + setLabel(i.getID()); + else{ + setLabel(InstructionNodeWidget.modifiyStringLength(i.getID()+": "+i.getInstructionString(), + InstructionNodeWidget.MAXSTRINGLENGTH,InstructionNodeWidget.MAXSTRINGLENGTH, + InstructionNodeWidget.ABBREV)); + } + + setFont(Font.decode(InstructionNodeWidget.DEFAULTFONT).deriveFont(Font.BOLD)); + setForeground(InstructionNodeWidget.getLineColor()); + setOpaque(false); + InstructionNodeWidget nw=s.getNodeWidget(i.getID()); + if(nw!=null){ + setToolTipText(nw.createToolTip()); + } + + Color c=InstructionNodeWidget.getColorByType(i.getInstructionType(),false); + Color dc=InstructionNodeWidget.getLineColor(); + setBorder(BorderFactory.createRoundedBorder(0,0,LEFTINSET,TOPINSET,c,dc)); + + } + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionConnectionWidget.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionConnectionWidget.java new file mode 100644 index 000000000000..c69440139ee7 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionConnectionWidget.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.graph; + +import java.awt.BasicStroke; +import java.awt.Color; +import org.netbeans.api.visual.widget.ConnectionWidget; +import org.netbeans.api.visual.widget.Scene; +import org.netbeans.api.visual.widget.Widget; + +/** + * + * @author Stefan Loidl + */ +public class InstructionConnectionWidget extends ConnectionWidget{ + + private static final BasicStroke EXPANDEDSTROKE=new BasicStroke(2.0f); + private static final BasicStroke UNEXPANDEDSTROKE=new BasicStroke(1.0f); + private static final BasicStroke SELECTEDSTROKE=new BasicStroke(3.0f); + + + private static final Color EXPANDEDCOLOR=new Color(200,0,0); + private static final Color UNEXPANDEDCOLOR=Color.BLACK; + private static final Color INTERCLUSTERCOLOR=Color.GRAY; + + //If one if the endpoints is selected + private static final Color SELECTEDCOLOR=Color.BLACK; + private static final Color SELECTEDBACKWARDCOLOR=Color.BLUE; + private static final Color INTERSELECTIONCOLOR=new Color(0,100,0); + + + + + + /** Creates a new instance of InstructionConnectionWidget */ + public InstructionConnectionWidget(Scene s) { + super(s); + } + + + protected void paintWidget(){ + + boolean expanded=false; + String cluster=null; + //is the link crossing cluster borders? + boolean interClusterLink=false; + ClusterWidget cw1=null,cw2=null; + boolean sourceSelected=false; + boolean targetSelected=false; + + + Scene scene=this.getScene(); + if(scene instanceof InstructionNodeGraphScene) + interClusterLink=((InstructionNodeGraphScene)scene).isInterClusterLinkGrayed(); + + + + Widget w=getSourceAnchor().getRelatedWidget(); + if(w instanceof InstructionNodeWidget){ + if(!((InstructionNodeWidget)w).isWidgetVisible()) return; + expanded=((InstructionNodeWidget)w).isPathHighlighted(); + cw1=((InstructionNodeWidget)w).getClusterWidget(); + } + sourceSelected=w.getState().isSelected(); + + + w=getTargetAnchor().getRelatedWidget(); + if(w instanceof InstructionNodeWidget){ + if(!((InstructionNodeWidget)w).isWidgetVisible()) return; + expanded = expanded && ((InstructionNodeWidget)w).isPathHighlighted(); + cw2=((InstructionNodeWidget)w).getClusterWidget(); + interClusterLink= interClusterLink && (cw1!=cw2); + } + targetSelected=w.getState().isSelected(); + + if(expanded) { + this.setStroke(EXPANDEDSTROKE); + this.setForeground(EXPANDEDCOLOR); + } + else if(sourceSelected && targetSelected){ + this.setStroke(SELECTEDSTROKE); + this.setForeground(INTERSELECTIONCOLOR); + } + else if(sourceSelected){ + this.setStroke(SELECTEDSTROKE); + this.setForeground(SELECTEDCOLOR); + } + else if(targetSelected){ + this.setStroke(SELECTEDSTROKE); + this.setForeground(SELECTEDBACKWARDCOLOR); + } + else { + this.setStroke(UNEXPANDEDSTROKE); + if(interClusterLink) this.setForeground(INTERCLUSTERCOLOR); + else this.setForeground(UNEXPANDEDCOLOR); + } + + super.paintWidget(); + } + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionNodeGraphScene.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionNodeGraphScene.java new file mode 100644 index 000000000000..5ecb4217fe88 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionNodeGraphScene.java @@ -0,0 +1,1332 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.graph; + +import at.ssw.positionmanager.Cluster; +import at.ssw.positionmanager.LayoutGraph; +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Port; +import at.ssw.positionmanager.Vertex; +import at.ssw.positionmanager.export.GMLFileExport; +import at.ssw.positionmanager.impl.GraphCluster; +import at.ssw.positionmanager.impl.GraphLink; +import at.ssw.positionmanager.impl.GraphPort; +import at.ssw.positionmanager.impl.GraphVertex; +import at.ssw.visualizer.dataflow.attributes.ExpandNodeSwitchAttribute; +import at.ssw.visualizer.dataflow.attributes.ExpandStructureAttribute; +import at.ssw.visualizer.dataflow.attributes.ISwitchAttribute; +import at.ssw.visualizer.dataflow.attributes.InvisibleAttribute; +import at.ssw.visualizer.dataflow.attributes.SelfSwitchingExpandAttribute; +import at.ssw.visualizer.dataflow.instructions.Instruction; +import at.ssw.dataflow.layout.ExternalGraphLayouter; +import at.ssw.visualizer.graphhelper.DiGraph; +import at.ssw.visualizer.graphhelper.Edge; +import at.ssw.visualizer.graphhelper.Node; +import java.awt.Color; +import java.awt.Font; +import java.awt.Point; +import java.awt.Rectangle; +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import javax.swing.JPopupMenu; +import javax.swing.JViewport; +import org.netbeans.api.visual.action.ActionFactory; +import org.netbeans.api.visual.action.EditProvider; +import org.netbeans.api.visual.action.MoveProvider; +import org.netbeans.api.visual.action.MoveStrategy; +import org.netbeans.api.visual.action.PopupMenuProvider; +import org.netbeans.api.visual.action.RectangularSelectDecorator; +import org.netbeans.api.visual.action.RectangularSelectProvider; +import org.netbeans.api.visual.action.SelectProvider; +import org.netbeans.api.visual.action.WidgetAction; +import org.netbeans.api.visual.anchor.Anchor; +import org.netbeans.api.visual.anchor.AnchorFactory; +import org.netbeans.api.visual.anchor.AnchorShape; +import org.netbeans.api.visual.border.BorderFactory; +import org.netbeans.api.visual.graph.GraphScene; +import org.netbeans.api.visual.graph.layout.TreeGraphLayout; +import org.netbeans.api.visual.layout.LayoutFactory; +import org.netbeans.api.visual.router.Router; +import org.netbeans.api.visual.router.RouterFactory; +import org.netbeans.api.visual.widget.ConnectionWidget; +import org.netbeans.api.visual.widget.LabelWidget; +import org.netbeans.api.visual.widget.LayerWidget; +import org.netbeans.api.visual.widget.Scene; +import org.netbeans.api.visual.widget.Widget; + +/** + * + * @author Stefan Loidl + */ +public class InstructionNodeGraphScene extends GraphScene implements RectangularSelectDecorator, + RectangularSelectProvider, + MoveStrategy, + EditProvider, SelectProvider{ + + //scene layers + private Widget mainLayer; + private Widget connectionLayer; + private Widget selectLayer; + private Widget clusterLayer; + + //Nodeclusters- not always used- layouter dependent + private Collection clusters=null; + //nodes- saperated to achive efficiency + private Hashtable nodewidgets; + + //Menu item strings + private String RELEASEINFTREE="Collapse Influence Tree"; + private String RELEASEPARENTINF="Collapse Parent Influence"; + private String RELEASECYCLES="Collapse Cycles"; + private String RELEASESINGLECYCLE="Collapse Single Cycle"; + private String RELEASENODE="Collapse Node"; + + private WidgetAction moveAction = ActionFactory.createMoveAction (this, new MultiWidgetMovementProvider()); + private WidgetAction selectAction= ActionFactory.createSelectAction(this); + + private ExternalGraphLayouter layouter=null; + + private LinkedList listeners; + + //Defines if links between clusters are grayed + private boolean interCluserLinkGray=true; + + //Used to determine planarity, cycles... + private DiGraph calculationModel; + + //Is layouting done automaticly? + private boolean autoLayout=true; + + //Are invisible Nodes layouted? + private boolean layoutInvisibleNodes=false; + + //Is highlight clustering used? + private boolean highlightClustering=false; + + //Are Clusterboarders visible + private boolean clusterBordersVisible=true; + + //Are Location animations used + private boolean locationAnimation=true; + + + //Default Block/Cluster names + private final static String HIGHLIGHTBLOCK="HIGHLIGHT"; + private final static String DEFAULTBLOCK="NULL"; + + //Are current node positions used by layouters to build on? + private boolean USECURRENTNODEPOSITIONS=false; + + private Router linkRouter= new at.ssw.visualizer.dataflow.graph.DirectLineRouter(this); + + + /** + * Constructor + */ + public InstructionNodeGraphScene () { + + clusterLayer=new LayerWidget(this); + addChild(clusterLayer); + mainLayer = new LayerWidget (this); + addChild (mainLayer); + connectionLayer = new Widget (this); + addChild (connectionLayer); + selectLayer=new LayerWidget(this); + addChild(selectLayer); + + getInputBindings().setZoomActionModifiers(0); + getActions().addAction(ActionFactory.createMouseCenteredZoomAction(1.2)); + getActions().addAction(ActionFactory.createPanAction()); + getActions().addAction(ActionFactory.createRectangularSelectAction(this, (LayerWidget) selectLayer,this)); + + nodewidgets=new Hashtable(); + listeners=new LinkedList(); + } + + /** + * Adds an array of instructions to the Scene. This is the only way nodes should + * be added to this type of scene. + */ + public void addInstructions(Instruction[] instructions){ + calculationModel=new DiGraph(); + + //Add nodes to scene + for(Instruction inst : instructions){ + addNode(inst); + + calculationModel.addNode(new Node(inst.getID())); + } + + //Add edges to scene + for(Instruction inst : instructions){ + for(Instruction x: inst.getSuccessors()){ + //in some cases it may happen that a phi expression + //uses the same instruction more than once + //edges would be duplicated. This if prevents this + if(!getEdges().contains(inst.getID()+x.getID())){ + addEdge(inst.getID()+x.getID()); + setEdgeSource(inst.getID()+x.getID(),inst); + setEdgeTarget(inst.getID()+x.getID(),x); + + + calculationModel.addEdge(new Edge(calculationModel.getNode(inst.getID()), + calculationModel.getNode(x.getID()))); + } + } + } + + //Add cycle information to each node + for(InstructionNodeWidget nw: getNodeWidgets()){ + Node n=calculationModel.getNode(nw.getID()); + if(n!=null){ + Collection cycles=calculationModel.getCircularDependency(n); + LinkedList intc=new LinkedList(); + InstructionNodeWidget[] wid; + + for(Node[] nl:cycles){ + wid=new InstructionNodeWidget[nl.length]; + for(int i=0;i < nl.length;i++){ + wid[i]=getNodeWidget(nl[i].ID); + } + intc.add(wid); + } + + nw.setCycles(intc); + } + } + } + + + /** + * Refreshes all node & cluster widget data + */ + public void refreshAll(){ + Enumeration enu=nodewidgets.elements(); + + while(enu.hasMoreElements()) { + InstructionNodeWidget w=enu.nextElement(); + w.refresh(); + } + for(Widget w: connectionLayer.getChildren()){ + w.revalidate(true); + } + + refreshClusterWidgets(); + refreshLayoutVertex(); + } + + + /** + * Updates the data of the layoutgraph verticles. + */ + protected void refreshLayoutVertex(){ + if(VertexToWidget!=null){ + for(Map.Entry e:VertexToWidget.entrySet()){ + if(e.getKey() instanceof GraphVertex){ + GraphVertex v=(GraphVertex)e.getKey(); + v.setDirty(true); + } + } + } + + } + + /** + * Refreshes the size calculation of the cluster widgets. + */ + protected void refreshClusterWidgets(){ + if(clusters!=null){ + for(ClusterWidget c:clusters){ + c.refresh(); + } + } + } + + /** + * Centers the view at point p. + */ + public void centerOn(Point p){ + Object o=getView().getParent(); + + if(o instanceof JViewport){ + JViewport v=(JViewport)o; + Point upperLeft=convertViewToScene(v.getViewPosition()); + Point lowerRight=v.getViewPosition(); + lowerRight.translate(v.getExtentSize().width,v.getExtentSize().height); + lowerRight=convertViewToScene(lowerRight); + + int x=p.x-(lowerRight.x-upperLeft.x)/2; + int y=p.y-(lowerRight.y-upperLeft.y)/2; + + Point n=convertSceneToView(new Point(x,y)); + + int maxX=v.getViewSize().width-v.getExtentSize().width; + int maxY=v.getViewSize().height-v.getExtentSize().height; + + if(n.x < 0) n.x=0; + if(n.x > maxX) n.x=maxX; + if(n.y < 0) n.y=0; + if(n.y > maxY) n.y=maxY; + + + v.setViewPosition(n); + } + } + + + /** + * Edit provider action- This method is called if a node is + * double clicked. + */ + public void edit(Widget widget) { + if(widget instanceof InstructionNodeWidget){ + fireDoubleClicked((InstructionNodeWidget)widget); + } + if(widget instanceof ClusterWidget){ + try{ + int x=widget.getPreferredLocation().x+widget.getPreferredBounds().width/2; + int y=widget.getPreferredLocation().y+widget.getPreferredBounds().height/2; + centerOn(new Point(x,y)); + + HashSet set=new HashSet(); + for(Widget w:((ClusterWidget)widget).getWidgets()){ + if(w instanceof InstructionNodeWidget){ + set.add(((InstructionNodeWidget)w).getInstruction()); + } + } + + setSelectedObjects(set); + fireSelectionChanged(); + refreshAll(); + validate(); + }catch(Exception e){ + } + } + } + + /** + * Exports the current layout graph into the GML file format. + */ + public boolean exportToGMLFile(File f){ + Hashtable names=new Hashtable(); + Hashtable colors=new Hashtable(); + + for(Vertex v:layoutGraph.getVertices()){ + InstructionNodeWidget w=VertexToWidget.get(v); + if(w!=null){ + names.put(v,VertexToWidget.get(v).getID()); + colors.put(v,InstructionNodeWidget.getColorByType(w.getInstruction().getInstructionType(),false)); + } + } + GMLFileExport exp=new GMLFileExport(f,names,colors); + return exp.export(layoutGraph); + } + + + // + /** + * This method expands the subtree with w as the root. Expansion is done + * via the internal attribute system. If forward is true the successors + * are used... if not the tree is traversed backward via Predecessors. + */ + protected void handleExpandInfluenceTree(Instruction i, boolean forward){ + String desc="("+i.getID()+")"; + + if(forward) desc=RELEASEINFTREE+desc; + else desc=RELEASEPARENTINF+desc; + + + ExpandNodeSwitchAttribute en= new ExpandNodeSwitchAttribute(true,desc,true, this); + + LinkedList list=new LinkedList(); + + getNodeWidget(i.getID()).addNodeAttribute(en); + + list.add(i); + + if(forward){ + for(Instruction inst: i.getSuccessors()){ + rek_ExpandInfluenceTree(inst,en,list, forward); + } + } else{ + for(Instruction inst: i.getPredecessors()){ + rek_ExpandInfluenceTree(inst,en,list, forward); + } + } + + refreshAll(); + validate(); + autoLayout(); + } + + private void rek_ExpandInfluenceTree(Instruction i, ISwitchAttribute a,LinkedList list, boolean forward){ + if(list.contains(i)) return; + list.add(i); + + ExpandStructureAttribute es=new ExpandStructureAttribute(a,this); + + getNodeWidget(i.getID()).addNodeAttribute(es); + + + if(forward){ + for(Instruction inst: i.getSuccessors()){ + rek_ExpandInfluenceTree(inst,a,list, forward); + } + } else{ + for(Instruction inst: i.getPredecessors()){ + rek_ExpandInfluenceTree(inst,a,list, forward); + } + } + } + + /** + * This Method expands a single Node + */ + protected void handleExpandNode(Instruction instruction) { + Set selected=(Set)getSelectedObjects(); + + //Prepare for multiselected usage + if(selected == null || selected.size()==0 || !selected.contains(instruction)){ + selected=new HashSet(); + selected.add(instruction); + } + + for(Instruction i: selected){ + InstructionNodeWidget n=getNodeWidget(i.getID()); + SelfSwitchingExpandAttribute en=new SelfSwitchingExpandAttribute(RELEASENODE+"("+i.getID()+")", this); + n.addNodeAttribute(en); + } + + refreshAll(); + validate(); + autoLayout(); + } + + + /** + * Add Expand attributes to the nodes making up cycles the the + * Node with the given id + */ + void handleExpandCycles(String id) { + InstructionNodeWidget nw=nodewidgets.get(id); + + if(nw!=null){ + String desc=RELEASECYCLES+" ("+id+")"; + ExpandNodeSwitchAttribute en= new ExpandNodeSwitchAttribute(true,desc,true, this); + nw.addNodeAttribute(en); + + + Collection c=nw.getCycleWidgets(); + LinkedList handled=new LinkedList(); + handled.add(nw); + + for(InstructionNodeWidget[] a:c){ + for(InstructionNodeWidget w: a){ + if(!handled.contains(w)){ + handled.add(w); + ExpandStructureAttribute es=new ExpandStructureAttribute(en,this); + w.addNodeAttribute(es); + } + } + } + } + + refreshAll(); + validate(); + autoLayout(); + } + + /** + * This Method expands the Cycle with given index of the node. + */ + public void handleExpandCycle(String node, int cycleIndex){ + InstructionNodeWidget nw=nodewidgets.get(node); + + if(nw!=null){ + String desc=RELEASESINGLECYCLE+" ("+node+"/"+cycleIndex+")"; + ExpandNodeSwitchAttribute en= new ExpandNodeSwitchAttribute(true,desc,true, this); + nw.addNodeAttribute(en); + + LinkedList handled=new LinkedList(); + handled.add(nw); + + int i=0; + for(InstructionNodeWidget[] a:nw.getCycleWidgets()){ + if(i==cycleIndex){ + for(InstructionNodeWidget w: a){ + if(!handled.contains(w)){ + handled.add(w); + ExpandStructureAttribute es=new ExpandStructureAttribute(en,this); + w.addNodeAttribute(es); + } + } + break; + } + i++; + } + } + refreshAll(); + validate(); + autoLayout(); + } + + /** + * Hides all nodes but the current one (or all selected if so) + */ + public void handleHideAllBut(Instruction instruction) { + Set selected=(Set)getSelectedObjects(); + boolean deselect=false; + + //Prepare for multiselected usage + if(selected.size()==0){ + selected=new HashSet(); + selected.add(instruction); + } else if(!selected.contains(instruction)){ + selected=new HashSet(); + selected.add(instruction); + }else{ + deselect=true; + } + + for(InstructionNodeWidget w:getNodeWidgets()){ + if(!(selected!=null && selected.contains(w.getInstruction()))){ + if(w.isWidgetVisible()) w.addNodeAttribute(new InvisibleAttribute()); + } + } + + if(deselect){ + //deselect all! + setSelectedObjects(new HashSet()); + fireSelectionChanged(); + } + + refreshAll(); + validate(); + autoLayout(); + } + + /** + * Handles the toggle visibility event. If includeSelection==ture selected + * widgets are included if they contain inst. + */ + public void handleToggleVisibility(Instruction inst, boolean includeSelection){ + Set selected=null; + if(includeSelection){ + selected=(Set)getSelectedObjects(); + + //Prepare for multiselected usage + if(selected.size()==0){ + selected=new HashSet(); + selected.add(inst); + } else if(!selected.contains(inst)){ + selected=new HashSet(); + selected.add(inst); + } + }else{ + selected=new HashSet(); + selected.add(inst); + } + + //If nodes get visible- selection has to be canceled + boolean unselect= false; + for(Instruction i:selected){ + InstructionNodeWidget nw=getNodeWidget(i.getID()); + if(nw.isWidgetVisible()) { + nw.addNodeAttribute(new InvisibleAttribute()); + unselect=true; + } + else { + nw.makeVisible(); + createCurrentLayoutGraph(); + } + } + + if(unselect){ + selected=new HashSet(); + setSelectedObjects(selected); + fireSelectionChanged(); + } + + refreshAll(); + validate(); + autoLayout(); + } + + + /** + * Makes the influence tree (forward==true) or the parent influence tree (==false) + * visible. + */ + public void handleMakeTreeVisible(Instruction inst, boolean forward){ + Set selected=(Set)getSelectedObjects(); + LinkedList handled= new LinkedList(); + + //Prepare for multiselected usage + if(selected.size()==0){ + selected=new HashSet(); + selected.add(inst); + } + else if(!selected.contains(inst)){ + selected=new HashSet(); + selected.add(inst); + } + + for(Instruction i: selected){ + InstructionNodeWidget nw=getNodeWidget(i.getID()); + nw.makeVisible(); + handled.add(i); + if(forward) for(Instruction x: i.getSuccessors()) recHandleMakeTreeVisible(x,forward,handled); + else for(Instruction x: i.getPredecessors()) recHandleMakeTreeVisible(x,forward,handled); + } + createCurrentLayoutGraph(); + refreshAll(); + validate(); + autoLayout(); + } + + private void recHandleMakeTreeVisible(Instruction inst, boolean forward, LinkedList handled){ + if(handled.contains(inst)) return; + handled.add(inst); + InstructionNodeWidget nw=getNodeWidget(inst.getID()); + nw.makeVisible(); + //set vertex dirty + if(WidgetToVertex!=null) { + Vertex v=WidgetToVertex.get(nw); + if(v!=null && v instanceof GraphVertex) ((GraphVertex)v).setDirty(true); + } + if(forward) for(Instruction x: inst.getSuccessors()) recHandleMakeTreeVisible(x,forward, handled); + else for(Instruction x: inst.getPredecessors()) recHandleMakeTreeVisible(x,forward, handled); + } + + /** + * Makes the Cycle Widgets visible- if cycleIndex==-1 then all cycles of the node + * are expanded. + */ + public void handleMakeCycleVisible(String node, int cycleIndex){ + InstructionNodeWidget nw=nodewidgets.get(node); + + if(nw!=null){ + nw.makeVisible(); + + int i=0; + for(InstructionNodeWidget[] a:nw.getCycleWidgets()){ + if(i==cycleIndex || cycleIndex==-1){ + for(InstructionNodeWidget w: a) { + w.makeVisible(); + } + } + i++; + } + } + createCurrentLayoutGraph(); + refreshAll(); + validate(); + autoLayout(); + } + + /** + * Hides all nodes of a specific type. + */ + public void handleSetVisibilityNodeType(boolean visible, Instruction.InstructionType type){ + + for(InstructionNodeWidget w: nodewidgets.values()){ + if(type==null || w.getInstruction().getInstructionType()==type){ + if(visible){ + w.makeVisible(); + } else{ + if(w.isWidgetVisible()) w.addNodeAttribute(new InvisibleAttribute()); + } + } + } + + createCurrentLayoutGraph(); + fireUpdateNodeData(); + } + + + /** + * Makes all nodes within the list visible + */ + void handleShowNodes(Instruction[] instruction) { + for(Instruction i: instruction){ + InstructionNodeWidget w=getNodeWidget(i.getID()); + if(w!=null){ + w.makeVisible(); + } + } + + createCurrentLayoutGraph(); + refreshAll(); + validate(); + autoLayout(); + } + // + + + // + + /** + * Performs a layout id autoLayout==true + */ + public void autoLayout() { + if(autoLayout){ + layout(); + } + } + + private LayoutGraph layoutGraph=null; + private Hashtable VertexToWidget=null; + private Hashtable WidgetToVertex=null; + private Hashtable clusterToId=null; + + /** + * Returns the current layoutgraph. + */ + public LayoutGraph getLayoutGraph(){ + return layoutGraph; + } + + /** + * This method returns the binding list of Widgets an Verticles of the layoutgraph. + */ + public Hashtable getWidgetToVertex(){ + return WidgetToVertex; + } + + /** + * This method fills the layoutGraph, clusterToId, WidgetToVertex and vertexToWidget Datastructures + */ + private void createCurrentLayoutGraph(){ + Hashtable idToVertex=new Hashtable(); + TreeSet links=new TreeSet(); + TreeSet verticles=new TreeSet(); + + clusterToId=new Hashtable(); + VertexToWidget=new Hashtable(); + WidgetToVertex=new Hashtable(); + + DiGraph model=calculationModel.clone(); + + //Remove all invisible verticles from the model + if(!layoutInvisibleNodes){ + for(InstructionNodeWidget nw:getNodeWidgets()){ + if(!nw.isWidgetVisible()) model.removeNode(model.getNode(nw.getID())); + } + } + + int i=0, j=0, ci=0; + //collect the link set + for(DiGraph dg:model.getConnectedComponents()){ + Hashtable idToCluster=new Hashtable(); + for(Edge e:dg.getEdges()){ + String nID1=e.source.ID; + String nID2=e.destination.ID; + Vertex v1,v2; + InstructionNodeWidget nw1=getNodeWidget(nID1); + InstructionNodeWidget nw2=getNodeWidget(nID2); + + if(idToVertex.containsKey(nID1)) v1=idToVertex.get(nID1); + else{ + Cluster c; + String block=nw1.getInstruction().getSourceBlock(); + if(block==null) block=DEFAULTBLOCK; + if(highlightClustering&&nw1.isPathHighlighted()) block=HIGHLIGHTBLOCK; + + if(idToCluster.containsKey(block)){ + c=idToCluster.get(block); + } else{ + c=new GraphCluster(ci++,null,null); + idToCluster.put(block,c); + clusterToId.put(c,block); + } + v1=new GraphVertex(i++,c,nw1.getLocation(),nw1); + idToVertex.put(nID1,v1); + VertexToWidget.put(v1,nw1); + WidgetToVertex.put(nw1,v1); + } + + if(idToVertex.containsKey(nID2)) v2=idToVertex.get(nID2); + else{ + Cluster c; + String block=nw2.getInstruction().getSourceBlock(); + if(block==null) block=DEFAULTBLOCK; + if(highlightClustering&&nw2.isPathHighlighted()) block=HIGHLIGHTBLOCK; + + if(idToCluster.containsKey(block)){ + c=idToCluster.get(block); + } else{ + c=new GraphCluster(ci++,null,null); + idToCluster.put(block,c); + clusterToId.put(c,block); + } + v2=new GraphVertex(i++,c,nw2.getLocation(),nw2); + idToVertex.put(nID2,v2); + VertexToWidget.put(v2,nw2); + WidgetToVertex.put(nw2,v2); + } + + Port p1=new GraphPort(v1); + Port p2=new GraphPort(v2); + + + //Add dataflow clusterlinks + Cluster source=v1.getCluster(); + Cluster target=v2.getCluster(); + ((Set)source.getSuccessors()).add(target); + ((Set)target.getPredecessors()).add(source); + + links.add(new GraphLink(j++,p1,p2)); + } + } + + //collect additional Verticles + for(Node n:model.getNodes()){ + if(n.edges.size()==0){ + InstructionNodeWidget nw=getNodeWidget(n.ID); + Cluster c=new GraphCluster(ci++,null,null); + Vertex v=new GraphVertex(i++,c,nw.getLocation(),nw); + verticles.add(v); + VertexToWidget.put(v,nw); + WidgetToVertex.put(nw,v); + idToVertex.put(n.ID,v); + String block=nw.getInstruction().getSourceBlock(); + if(block==null) block=DEFAULTBLOCK; + if(highlightClustering&&nw.isPathHighlighted()) block=HIGHLIGHTBLOCK; + clusterToId.put(c,block); + } + } + + layoutGraph=new LayoutGraph(links,verticles); + for(Link l: layoutGraph.getLinks()){ + ((GraphPort)l.getFrom()).setLayoutGraph(layoutGraph); + ((GraphPort)l.getTo()).setLayoutGraph(layoutGraph); + } + + } + + /** Performs the layout according to the External Layouter */ + public void layout(){ + //fill datastructures: layoutgraph, VertexToWidget and clusterToId + createCurrentLayoutGraph(); + //Do layouting step! + removeClusterWidgets(); + + layouter.setUseCurrentNodePositions(USECURRENTNODEPOSITIONS); + layouter.doLayout(layoutGraph); + + + //Add cluster widgets + if(layouter.isClusteringSupported()){ + Hashtable> clusters=new Hashtable>(); + for(Vertex v:layoutGraph.getVertices()){ + if(!clusters.containsKey(v.getCluster())){ + clusters.put(v.getCluster(),new LinkedList()); + } + clusters.get(v.getCluster()).add(VertexToWidget.get(v)); + } + + LinkedList widgets=new LinkedList(); + for(Cluster c :clusters.keySet()){ + LinkedList l=clusters.get(c); + String id=clusterToId.get(c); + Color col=null; + if(HIGHLIGHTBLOCK.equals(id)) col=Color.RED; + else col=Color.BLUE; + widgets.add(new ClusterWidget(id,l,this,col)); + } + + setClustersWidgets(widgets); + } + + + + + //Set the real positions of the graph + if(locationAnimation && layouter.isAnimationSupported()){ + SetLocationAnimator animator=new SetLocationAnimator(this,VertexToWidget); + animator.animate(); + } + else{ + for(Vertex v: layoutGraph.getVertices()){ + InstructionNodeWidget nw=VertexToWidget.get(v); + //Instruction widget inset correction: Neccessary because layouter do not support insets! + Point pos=v.getPosition(); + pos.translate(InstructionNodeWidget.BORDERINSET,InstructionNodeWidget.BORDERINSET); + nw.setPreferredLocation(pos); + if(v instanceof GraphVertex) ((GraphVertex)v).setDirty(true); + } + refreshClusterWidgets(); + } + + validate(); + + } + + /** + * Sets the cluster widgets, this method is meant to be + * used within the external layouter. + */ + public void setClustersWidgets(Collection clusters){ + removeClusterWidgets(); + if(clusters!=null){ + this.clusters=clusters; + + for(ClusterWidget w:clusters) { + clusterLayer.addChild(w); + w.setVisibility(clusterBordersVisible); + w.refresh(); + for(Widget iw:w.getWidgets()){ + if(iw instanceof InstructionNodeWidget) + ((InstructionNodeWidget)iw).setClusterWidget(w); + } + + } + } + fireUpdateNodeData(); + } + + + /** + * Removes all clusters. This method is called + * before layouting is done. + */ + public void removeClusterWidgets(){ + if(clusters!=null){ + for(ClusterWidget w:clusters){ + clusterLayer.removeChild(w); + } + for(InstructionNodeWidget w:nodewidgets.values()){ + w.setClusterWidget(null); + } + } + clusters=null; + fireUpdateNodeData(); + } + // + + + // + /** + * Implemented to achive a clear cut between data model and visualisation. + * Otherwise the Instruction class would have to save position data. + */ + public InstructionNodeWidget getNodeWidget(String i){ + return nodewidgets.get(i); + } + + /** + * Returns all Nodewidgets + */ + public InstructionNodeWidget[] getNodeWidgets(){ + return nodewidgets.values().toArray(new InstructionNodeWidget[nodewidgets.size()]); + } + + /** Sets the layouter */ + public void setExternalLayouter(ExternalGraphLayouter l){ + layouter=l; + } + + /** Return the current layouter */ + public ExternalGraphLayouter getExternalLayouter(){ + return layouter; + } + + /** + * Defines if links between different clusters are grayed. + */ + public void setInterClusterLinkGrayed(boolean b){ + interCluserLinkGray=b; + } + + /** + * Returns if links between different clusters are grayed + */ + public boolean isInterClusterLinkGrayed(){ + return interCluserLinkGray; + } + + /** + * Defines if scene performs autolayout. + */ + public void setAutoLayout(boolean b){ + autoLayout=b; + autoLayout(); + + } + + /** + * Is Auto Layout active?. + */ + public boolean isAutoLayout(){ + return autoLayout; + } + + + /** + * Defines if invisible nodes are layouted. + */ + public void setLayoutInvisibleNodes(boolean b){ + layoutInvisibleNodes=b; + } + + /** + * Returns if invisible Nodes are layouted or not. + */ + public boolean isLayoutInvisibleNodes(){ + return layoutInvisibleNodes; + } + + /** + * Defines if node animation is used. + */ + public void setNodeAnimation(boolean b){ + locationAnimation=b; + } + + /** + * Returns if invisible Nodes are layouted or not. + */ + public boolean isNodeAnimation(){ + return locationAnimation; + } + + /** + * Returns a clone of the calculation model of the scene + */ + public DiGraph getCaluculationModel(){ + return calculationModel.clone(); + } + + /** + * Defines if highlightclustering is used. + */ + public void setHighlightClustering(boolean b){ + highlightClustering=b; + } + + /** + * Returns if highlightclustering is active + */ + public boolean isHighlightClustering(){ + return highlightClustering; + } + + /** + * Returns if cluster borders are visible + */ + public boolean isClusterBordersVisible(){ + return clusterBordersVisible; + } + + /** + * Defines if the layout algorithms should build on the current node positions. + * This does only applys to layout-algorithms optimizing from a predefined positioning + * (like force layouts) + */ + public void setUseCurrentNodePositions(boolean b){ + USECURRENTNODEPOSITIONS=b; + } + + /** + * Returns if the current node position is used to build on by the external layouter. + * This does only applys to layout-algorithms optimizing from a predefined positioning + * (like force layouts) + */ + public boolean isUseCurrentNodePositions(){ + return USECURRENTNODEPOSITIONS; + } + + + /** + * Sets is clusters borders are shown or not. + */ + public void setClusterBordersVisible(boolean b){ + clusterBordersVisible=b; + if(clusters!=null){ + for(ClusterWidget w: clusters){ + w.setVisibility(b); + } + } + refreshAll(); + validate(); + } + // + + + // + /** + * Registers an scene listener + */ + public void addInstructionSceneListener(InstructionSceneListener l){ + if(!listeners.contains(l)){ + listeners.add(l); + } + } + + /** + * Removes a scene listener + */ + public void removeInstructionSceneListener(InstructionSceneListener l){ + listeners.remove(l); + } + + /** + * Fires the double clicked event + */ + protected void fireDoubleClicked(InstructionNodeWidget w){ + for(InstructionSceneListener l: listeners){ + l.doubleClicked(w); + } + } + + /** + * Fires the update node data event + */ + protected void fireUpdateNodeData(){ + for(InstructionSceneListener l: listeners){ + l.updateNodeData(); + } + } + + /** + * Fires the selection changed event + */ + protected void fireSelectionChanged(){ + for(InstructionSceneListener l: listeners){ + HashSet w=new HashSet(); + for(Object o:getSelectedObjects()){ + if(o instanceof Instruction){ + InstructionNodeWidget wi=getNodeWidget(((Instruction)o).getID()); + if(wi != null) w.add(wi); + } + } + l.selectionChanged(w); + } + } + + // + + + // + /** + * RectangularSelect Method returning a widget to be use as + * selection boarder + */ + public Widget createSelectionWidget() { + Widget ret=new Widget(this); + ret.setBorder(BorderFactory.createDashedBorder(Color.BLACK,10,5,true)); + return ret; + } + + /** + * RectangularSelect execution + */ + public void performSelection(Rectangle rectangle) { + HashSet set=new HashSet(); + + //change to a rectangle with (x,y) at the top left + if(rectangle.width<0){ + rectangle.x=rectangle.x+rectangle.width; + rectangle.width*=-1; + } + if(rectangle.height<0){ + rectangle.y=rectangle.y+rectangle.height; + rectangle.height*=-1; + } + + for(InstructionNodeWidget n:nodewidgets.values()){ + if(n.isWidgetVisible() && rectangle.contains(n.getPreferredLocation())) + set.add(n.getInstruction()); + } + + setSelectedObjects(set); + refreshAll(); + validate(); + fireSelectionChanged(); + } + + /** MoveStrategy- free move, no modifications*/ + public Point locationSuggested(Widget widget, Point original, Point suggested) { + return suggested; + } + + /** + * Selectes exactly one widget defined by the id and centers the view + * on it if centeron==true. + */ + public void setSingleSelectedWidget(String id, boolean centeron) { + InstructionNodeWidget node=getNodeWidget(id); + + if(node!=null && node.isWidgetVisible()) { + setSelectedObjects(Collections.singleton(node.getInstruction())); + refreshAll(); + validate(); + fireSelectionChanged(); + if(centeron) centerOn(node.getPreferredLocation()); + } + } + + /* Single click selection method- Copy from ObjectScene Selectprovider*/ + public boolean isAimingAllowed(Widget widget, Point point, boolean b) { + return false; + } + + /* Single click selection method- Copy from ObjectScene Selectprovider*/ + public boolean isSelectionAllowed(Widget widget, Point point, boolean b) { + Object object = findObject (widget); + return object != null && (b || ! getSelectedObjects ().contains (object)); + } + + /* Single click selection method- Copy from ObjectScene Selectprovider*/ + public void select(Widget widget, Point point, boolean b) { + Object object = findObject (widget); + if (object != null) { + if (getSelectedObjects ().contains (object)) return; + userSelectionSuggested (Collections.singleton (object), b); + } else + userSelectionSuggested (Collections.emptySet (), b); + fireSelectionChanged(); + } + + + + /** + * This class implements a MoveProvider to achieve the funktionality of + * a multi widget movement. + */ + private class MultiWidgetMovementProvider implements MoveProvider{ + + private MoveProvider mp=ActionFactory.createDefaultMoveProvider(); + + public void movementStarted(Widget widget) { + mp.movementStarted(widget); + } + + public void movementFinished(Widget widget) { + mp.movementFinished(widget); + } + + public Point getOriginalLocation(Widget widget) { + return widget.getPreferredLocation();//mp.getOriginalLocation(widget); + } + + public void setNewLocation(Widget widget, Point point) { + //test for movement support + Scene s=widget.getScene(); + if(s instanceof InstructionNodeGraphScene){ + boolean r=((InstructionNodeGraphScene)s).getExternalLayouter().isMovementSupported(); + if(!r) return; + } + //perform movement + Point original=getOriginalLocation(widget); + int dx=point.x-original.x; + int dy=point.y-original.y; + for(Object o: getSelectedObjects()){ + if(o instanceof Instruction){ + InstructionNodeWidget w=getNodeWidget(((Instruction)o).getID()); + if(w!=widget){ + Point p=(Point)w.getPreferredLocation().clone(); + p.translate(dx,dy); + + //Update layoutgraph Positions + Vertex v=WidgetToVertex.get(w); + if(v!=null) v.setPosition(p); + ((GraphVertex)v).setDirty(true); + + w.setPreferredLocation(p); + w.refresh(); + ClusterWidget cw=w.getClusterWidget(); + if(cw!=null) cw.refresh(); + } + } + + } + mp.setNewLocation(widget,point); + + //Update layoutgraph Positions + Vertex v=WidgetToVertex.get(widget); + if(v!=null) v.setPosition(point); + ((GraphVertex)v).setDirty(true); + + //Refresh of cluster for single node movement + if(widget instanceof InstructionNodeWidget){ + ClusterWidget cw=((InstructionNodeWidget)widget).getClusterWidget(); + if(cw!=null) cw.refresh(); + } + } + } + + // + + + protected Widget attachNodeWidget (Instruction node) { + + InstructionNodeWidget widget = new InstructionNodeWidget (node,this); + + WidgetAction.Chain actions = widget.getActions (); + actions.addAction (createObjectHoverAction ()); + + actions.addAction (ActionFactory.createSelectAction(this)); + //actions.addAction(createSelectAction()); + actions.addAction (moveAction); + + actions.addAction(ActionFactory.createEditAction(this)); + + nodewidgets.put(node.getID(),widget); + mainLayer.addChild (widget); + return widget; + } + + + protected void attachEdgeSourceAnchor (String edge, Instruction oldSourceNode, Instruction sourceNode) { + ConnectionWidget edgeWidget = (ConnectionWidget) findWidget (edge); + Widget sourceNodeWidget = findWidget (sourceNode); + Anchor sourceAnchor = AnchorFactory.createRectangularAnchor (sourceNodeWidget); + edgeWidget.setSourceAnchor (sourceAnchor); + } + + protected void attachEdgeTargetAnchor (String edge, Instruction oldTargetNode, Instruction targetNode) { + ConnectionWidget edgeWidget = (ConnectionWidget) findWidget (edge); + Widget targetNodeWidget = findWidget (targetNode); + Anchor targetAnchor = AnchorFactory.createRectangularAnchor (targetNodeWidget); + edgeWidget.setTargetAnchor (targetAnchor); + } + + protected Widget attachEdgeWidget(String edge) { + ConnectionWidget widget = new InstructionConnectionWidget (this); + widget.setTargetAnchorShape (AnchorShape.TRIANGLE_FILLED); + connectionLayer.addChild (widget); + widget.setRouter(linkRouter); + return widget; + } + + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionNodeWidget.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionNodeWidget.java new file mode 100644 index 000000000000..9c2c9a75dafa --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionNodeWidget.java @@ -0,0 +1,879 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.graph; + +import at.ssw.visualizer.dataflow.attributes.IAdditionalWidgetAttribute; +import at.ssw.visualizer.dataflow.attributes.IExpandNodeAttribute; +import at.ssw.visualizer.dataflow.attributes.INodeAttribute; +import at.ssw.visualizer.dataflow.attributes.IInvisibilityAttribute; +import at.ssw.visualizer.dataflow.attributes.IPathHighlightAttribute; +import at.ssw.visualizer.dataflow.attributes.IPopupContributorAttribute; +import at.ssw.visualizer.dataflow.attributes.InvisibleAttribute; +import at.ssw.visualizer.dataflow.instructions.Instruction; +import java.awt.Color; +import java.awt.Font; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.event.MenuKeyEvent; +import javax.swing.event.MenuKeyListener; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import org.netbeans.api.visual.action.ActionFactory; +import org.netbeans.api.visual.action.PopupMenuProvider; +import org.netbeans.api.visual.border.Border; +import org.netbeans.api.visual.border.BorderFactory; +import org.netbeans.api.visual.layout.LayoutFactory; +import org.netbeans.api.visual.widget.LabelWidget; +import org.netbeans.api.visual.widget.Scene; +import org.netbeans.api.visual.widget.SeparatorWidget; +import org.netbeans.api.visual.widget.Widget; + +/** + * + * @author Stefan Loidl + */ +public class InstructionNodeWidget extends Widget{ + + /*Font of the nodes*/ + protected final static String DEFAULTFONT = "Arial-12"; + /*This string is used if text in the node is abbriviated*/ + protected final static String ABBREV="..."; + //Length bounds of text within nodes + protected final static int MAXSTRINGLENGTH=15; + protected final static int MINSTRINGLENGTH=7; + + + //Colors + private static Color colorParameter=new Color(255,230,200); + private static Color colorParameterSelect=new Color(240,180,0); + private static Color colorConstant=new Color(255,200,200); + private static Color colorConstantSelect=new Color(240,0,0); + private static Color colorPhi=new Color(200,200,255); + private static Color colorPhiSelect=new Color(0,0,240); + private static Color colorOperation=new Color(200,255,200); + private static Color colorOperationSelect=new Color(0,240,0); + private static Color colorControlFlow=new Color(210,255,255); + private static Color colorControlFlowSelect=new Color(0,240,200); + private static Color colorDefault=new Color(255,255,255); + private static Color colorDefaultSelect=new Color(245,245,245); + + private static Color colorLine=Color.BLACK; + private static Color colorHLLine=Color.GRAY; + + + //Because Borders can only be created by Factorys, but colors cannot be + //changed afterwards- to reduce GC Load Boarders are created only once! + private Border selectedBorder; + private Border selectedHLBorder; + private Border border; + private Border HLBorder; + private Border emptyBorder; + + public static final int BORDERARC=10; + public static final int BORDERINSET=5; + + //popup of the node widget + private JPopupMenu popup; + + //default menu item names + private static final String EXPANDINFLUENCE="Expand Influenced Tree"; + private static final String EXPANDNODE="Expand Node"; + private static final String EXPANDPARENTINFLUENCE="Expand Parent Influence"; + private static final String EXPANDCYCLES="Expand Cycles"; + + private static final String ALL="(All)"; + + private static final String TOGGLEVISIBILITY="Toggle Visibility"; + private static final String HIDEALLBUT="Hide All But Current"; + private static final String SHOWINFLUENCE="Show Influence Tree"; + private static final String SHOWPARENTINFLUENCE="Show Parent Influence"; + private static final String SHOWCYCLES="Show Cycles"; + private static final String SHOWPREDECESSORS="Show Predecessors"; + private static final String SHOWSUCCESSORS="Show Successors"; + + + private static final String CYCLEITEMSEPERATOR="-"; + private static final char CYCLECMDSEPERATOR='-'; + + + //Sub Menu within the popup + private static final String GOTOSUCCESSORS="Go to Successor"; + private static final String GOTOPREDECESSOR="Go to Predecessor"; + private static final String VISIBILITY="Visibility"; + + + + private LabelWidget lwID; + private LabelWidget lwInstruction; + private LabelWidget lwBlock; + + //indicate if predecessors or sucessors exist. + private Widget pred, suc; + + //Data model for the node. + private Instruction instruction; + + //Attributelist + private LinkedList attributeList; + + //Widgets that are appended to the standard view + private LinkedList additionalWidgets; + + //true if the node contains an IPathHighlightAttribute + private boolean pathHighlighted=false; + + //Inforation of cycles the node is within. + private Collection cycles=null; + + //Cluster the node is within + private ClusterWidget cluster; + + //This value is set during setWidgetData method + private boolean expanded=false; + + + /** Creates a new instance of InstructionNodeWidget */ + public InstructionNodeWidget(Instruction i, Scene s) { + super(s); + instruction=i; + + additionalWidgets=new LinkedList(); + attributeList=new LinkedList(); + + //initialize popup menu + popup=new JPopupMenu(); + getActions().addAction(ActionFactory.createPopupMenuAction(new PopupMenuProvider() { + public JPopupMenu getPopupMenu(Widget widget, Point point) { + return getPopup(); + } + })); + + this.setLayout(LayoutFactory.createVerticalFlowLayout()); + + lwID= new LabelWidget(s,i.getID()); + lwID.setFont(Font.decode(DEFAULTFONT).deriveFont(Font.BOLD)); + this.addChild (lwID); + lwID.resolveBounds(lwID.getLocation(),lwID.getPreferredBounds()); + + lwInstruction=new LabelWidget(s,modifiyStringLength(i.getInstructionString(),MINSTRINGLENGTH,MAXSTRINGLENGTH,ABBREV)); + lwInstruction.setFont(Font.decode(DEFAULTFONT)); + lwInstruction.setAlignment(LabelWidget.Alignment.CENTER); + this.addChild(lwInstruction); + lwInstruction.resolveBounds(lwInstruction.getLocation(),lwInstruction.getPreferredBounds()); + + lwBlock=new LabelWidget(s,i.getSourceBlock()); + lwBlock.setFont(Font.decode(DEFAULTFONT)); + this.addChild(lwBlock); + lwBlock.resolveBounds(lwBlock.getLocation(),lwBlock.getPreferredBounds()); + + pred=new HiddenNodesWidget(s,true); + suc=new HiddenNodesWidget(s,false); + + this.setToolTipText(createToolTip()); + + initWidgetBorders(); + setWidgetData(); + } + + + // + /** + * Return the color that is used to paint a node of specific type. + */ + protected static Color getColorByType(Instruction.InstructionType type, boolean selected){ + switch(type){ + case PARAMETER: + if(selected) return colorParameterSelect; + else return colorParameter; + case CONSTANT: + if(selected) return colorConstantSelect; + else return colorConstant; + case PHI: + if(selected) return colorPhiSelect; + else return colorPhi; + case CONTROLFLOW: + if(selected) return colorControlFlowSelect; + else return colorControlFlow; + case OPERATION: + if(selected) return colorOperationSelect; + else return colorOperation; + default: + if(selected) return colorDefaultSelect; + else return colorDefault; + } + } + + /** + * Returns the line color to be used in the widget. + */ + protected static Color getLineColor(){ + return colorLine; + } + + /** + * This function sets the borders (highlighted and selected combinations); + */ + private void initWidgetBorders(){ + Color c=getColorByType(instruction.getInstructionType(),false); + Color cs=getColorByType(instruction.getInstructionType(),true); + + border= BorderFactory.createRoundedBorder(BORDERARC,BORDERARC, + BORDERINSET,BORDERINSET,c,colorLine); + selectedBorder= BorderFactory.createRoundedBorder(BORDERARC,BORDERARC, + BORDERINSET,BORDERINSET,cs,colorLine); + HLBorder= BorderFactory.createRoundedBorder(BORDERARC,BORDERARC, + BORDERINSET,BORDERINSET,c,colorHLLine); + selectedHLBorder= BorderFactory.createRoundedBorder(BORDERARC,BORDERARC, + BORDERINSET,BORDERINSET,cs,colorHLLine); + + emptyBorder=BorderFactory.createEmptyBorder(); + } + + + + /** + * This method handles the the Data that is shown within the widget. This + * data depends on the Attibutes of the Widget. + */ + private void setWidgetData(){ + boolean visible=true, showBlock=false, showInstr=false; + additionalWidgets.clear(); + pathHighlighted=false; + + //determine node attibutes + Iterator iter=attributeList.iterator(); + while(iter.hasNext()){ + INodeAttribute a=iter.next(); + + if(!a.validate()) { + if(a.removeable()) { + iter.remove(); + } + + continue; + } + + if(a instanceof IInvisibilityAttribute) visible=false; + if(a instanceof IExpandNodeAttribute){ + if(((IExpandNodeAttribute)a).showBlock()) showBlock=true; + if(((IExpandNodeAttribute)a).showInstruction()) showInstr=true; + } + + if(a instanceof IPathHighlightAttribute) pathHighlighted=true; + + if(a instanceof IAdditionalWidgetAttribute){ + additionalWidgets.add(((IAdditionalWidgetAttribute)a).getWidget()); + } + } + + removeChildren(); + + if(visible){ + //Add invisible predecessor indicator + InstructionNodeGraphScene s=(InstructionNodeGraphScene) getScene(); + for(Instruction i: instruction.getPredecessors()){ + InstructionNodeWidget w=s.getNodeWidget(i.getID()); + if(!(w==null) && !w.isWidgetVisible()){ + addChild(pred); + break; + } + } + + expanded=false; + addChild(lwID); + if(showInstr) { + addChild(lwInstruction); + expanded=true; + } + if(showBlock) { + addChild(lwBlock); + expanded=true; + } + //Embedded widgets are only shown if expanded + if(showInstr){ + Iterator iterator =additionalWidgets.iterator(); + while(iterator.hasNext()){ + addChild(iterator.next()); + } + } + + //Add invisible sucessor indicator + for(Instruction i: instruction.getSuccessors()){ + InstructionNodeWidget w=s.getNodeWidget(i.getID()); + if(!(w==null) && !w.isWidgetVisible()){ + addChild(suc); + break; + } + } + } + + setWidgetBorder(); + resolveBounds(getLocation(),calculatePreferredBounds()); + } + + /** + * NOTE: This method is a only slightly changed copy an paste from Widget!!! + * Instead of getBounds- getPreferredBounds is used. + */ + private Rectangle calculatePreferredBounds () { + Insets insets = border.getInsets (); + Rectangle clientArea = calculateClientArea (); + + + + for (Widget child : getChildren()) { + Point location = child.getLocation (); + Rectangle bounds = child.getPreferredBounds (); + bounds.translate (location.x, location.y); + clientArea.add (bounds); + } + clientArea.x -= insets.left; + clientArea.y -= insets.top; + clientArea.width += insets.left + insets.right; + clientArea.height += insets.top + insets.bottom; + return clientArea; + } + + + /** + * This Method handels the Widgets border and coloring depending on hovering + * and selection state. + */ + private void setWidgetBorder(){ + if(isWidgetVisible()){ + if(this.getState().isHovered()){ + + if(this.getState().isSelected()) + this.setBorder(selectedHLBorder); + else + this.setBorder(HLBorder); + + lwID.setForeground(colorHLLine); + lwInstruction.setForeground(colorHLLine); + lwBlock.setForeground(colorHLLine); + } else{ + + if(this.getState().isSelected()) + this.setBorder(selectedBorder); + else + this.setBorder(border); + + lwID.setForeground(colorLine); + lwInstruction.setForeground(colorLine); + lwBlock.setForeground(colorLine); + } + } + else this.setBorder(emptyBorder); + } + + // + + + /** + * This Method is used to unifiy the length of Stings. Therefore a String + * is modified according to the following rules: + * 1. If in==null -> Return a String with min blanks + * 2. If length of in is grater max then in is cut and abbrev is concatinated + * to indicate that the String was shortend + * 3. If Length is smaller than min blanks are added + */ + protected static String modifiyStringLength(String in, int min, int max, String abbrev){ + StringBuffer mod; + if(in == null) + mod=new StringBuffer(""); + else + mod=new StringBuffer(in); + + if(mod.length() max){ + mod=new StringBuffer(mod.substring(0,max-abbrev.length())+abbrev); + } + + return mod.toString(); + } + + /** + * Returns the instruction which this nodewidget is a representation for. + */ + public Instruction getInstruction(){ + return instruction; + } + + /** + * Overwritten from parent class- Painting the widget + */ + protected void paintWidget(){ + setWidgetBorder(); + super.paintWidget(); + } + + + /** + * Is the node visible? + */ + public boolean isWidgetVisible(){ + Iterator iter=attributeList.iterator(); + while(iter.hasNext()){ + INodeAttribute a=iter.next(); + if(a instanceof IInvisibilityAttribute && a.validate()) return false; + } + return true; + } + + /** + * Removes all invisible attributes from the widget. + */ + public void makeVisible(){ + for(INodeAttribute att: attributeList.toArray(new INodeAttribute[attributeList.size()])){ + if(att instanceof IInvisibilityAttribute && att.removeable()) attributeList.remove(att); + } + } + + /** + * Returns if the instruction-string or basic-block is shown. + */ + public boolean isExpanded(){ + return expanded; + } + + /** + * Adds a new attriute to the node. + */ + public void addNodeAttribute(INodeAttribute a){ + if(a!=null) attributeList.add(a); + } + + /** + * Refreshes the visual appearence of the node and schedules it for + * revalidation. + */ + protected void refresh() { + setWidgetData(); + revalidate(); + } + + /** + * This method fills the popup menu. + */ + private void rebuildPopupMenu(){ + popup.removeAll(); + JMenuItem item; + JMenu menu; + + + //MENU Go to Predecessor + menu=new JMenu(GOTOPREDECESSOR); + if(instruction.getPredecessors().length==0){ + menu.setEnabled(false); + } else{ + for(Instruction i: instruction.getPredecessors()){ + item=new JMenuItem(i.getID()); + item.setActionCommand(i.getID()); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + InstructionNodeGraphScene s=(InstructionNodeGraphScene)getScene(); + s.setSingleSelectedWidget(e.getActionCommand(), true); + //Make the new node edited -> selected in the list + s.edit(s.getNodeWidget(e.getActionCommand())); + } + }); + menu.add(item); + } + } + popup.add(menu); + + //MENU Go to Successor + menu=new JMenu(GOTOSUCCESSORS); + if(instruction.getSuccessors().length==0){ + menu.setEnabled(false); + } else{ + for(Instruction i: instruction.getSuccessors()){ + item=new JMenuItem(i.getID()); + item.setActionCommand(i.getID()); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + InstructionNodeGraphScene s=(InstructionNodeGraphScene)getScene(); + s.setSingleSelectedWidget(e.getActionCommand(), true); + //Make the new node edited -> selected in the list + s.edit(s.getNodeWidget(e.getActionCommand())); + } + }); + menu.add(item); + } + } + popup.add(menu); + popup.addSeparator(); + + + //MENUITEM: Expand Node + item =new JMenuItem(EXPANDNODE); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + Scene s=getScene(); + if(s instanceof InstructionNodeGraphScene){ + ((InstructionNodeGraphScene)s).handleExpandNode(instruction); + } + } + }); + popup.add(item); + + //MENUITEM: Expand Influence Tree + item =new JMenuItem(EXPANDINFLUENCE); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + Scene s=getScene(); + if(s instanceof InstructionNodeGraphScene){ + ((InstructionNodeGraphScene)s).handleExpandInfluenceTree(instruction, true); + } + } + }); + popup.add(item); + item.setEnabled(instruction.getSuccessors().length>0); + + + //MENUITEM: Expand Parent Influence + item =new JMenuItem(EXPANDPARENTINFLUENCE); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + Scene s=getScene(); + if(s instanceof InstructionNodeGraphScene){ + ((InstructionNodeGraphScene)s).handleExpandInfluenceTree(instruction,false); + } + } + }); + popup.add(item); + item.setEnabled(instruction.getPredecessors().length>0); + + + + //MENU Expand Cycles + menu=new JMenu(EXPANDCYCLES); + if(cycles==null || cycles.size()==0){ + menu.setEnabled(false); + } else{ + + //Expand all Cycles + item =new JMenuItem(ALL); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + Scene s=getScene(); + if(s instanceof InstructionNodeGraphScene){ + ((InstructionNodeGraphScene)s).handleExpandCycles(getID()); + } + } + }); + menu.add(item); + + int i=0; + for(InstructionNodeWidget[] nw: cycles){ + StringBuffer st=new StringBuffer(); + for(InstructionNodeWidget n:nw){ + st.append(n.getID()); + st.append(CYCLEITEMSEPERATOR); + } + st.setLength(st.length()-CYCLEITEMSEPERATOR.length()); + + item=new JMenuItem(st.toString()); + item.setActionCommand(getID()+CYCLECMDSEPERATOR+String.valueOf(i)); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + InstructionNodeGraphScene s=(InstructionNodeGraphScene)getScene(); + String com=e.getActionCommand(); + int index=com.indexOf(CYCLECMDSEPERATOR); + if(index!=-1){ + try{ + s.handleExpandCycle(com.substring(0,index), + Integer.parseInt(com.substring(index+1))); + }catch(Exception ex){} + } + } + }); + menu.add(item); + i++; + } + } + popup.add(menu); + popup.addSeparator(); + + //ITEM Toggle Visibility + item =new JMenuItem(TOGGLEVISIBILITY); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + InstructionNodeGraphScene s=(InstructionNodeGraphScene)getScene(); + s.handleToggleVisibility(instruction, true); + + } + }); + popup.add(item); + + //ITEM Hide All BuT + item =new JMenuItem(HIDEALLBUT); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + InstructionNodeGraphScene s=(InstructionNodeGraphScene)getScene(); + s.handleHideAllBut(instruction); + } + }); + popup.add(item); + + //ITEM: Make Influence Tree Visible + item =new JMenuItem(SHOWINFLUENCE); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + InstructionNodeGraphScene s=(InstructionNodeGraphScene)getScene(); + s.handleMakeTreeVisible(instruction,true); + } + }); + popup.add(item); + item.setEnabled(instruction.getSuccessors().length>0); + + //ITEM: Make Parent Influence Visible + item =new JMenuItem(SHOWPARENTINFLUENCE); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + InstructionNodeGraphScene s=(InstructionNodeGraphScene)getScene(); + s.handleMakeTreeVisible(instruction,false); + } + }); + popup.add(item); + item.setEnabled(instruction.getPredecessors().length>0); + + + //MENU Make Cycles Visible + menu=new JMenu(SHOWCYCLES); + if(cycles==null || cycles.size()==0){ + menu.setEnabled(false); + } else{ + + //Expand all Cycles + item =new JMenuItem(ALL); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + Scene s=getScene(); + if(s instanceof InstructionNodeGraphScene){ + ((InstructionNodeGraphScene)s).handleMakeCycleVisible(getID(),-1); + } + } + }); + menu.add(item); + + int i=0; + for(InstructionNodeWidget[] nw: cycles){ + StringBuffer st=new StringBuffer(); + for(InstructionNodeWidget n:nw){ + st.append(n.getID()); + st.append(CYCLEITEMSEPERATOR); + } + st.setLength(st.length()-CYCLEITEMSEPERATOR.length()); + + item=new JMenuItem(st.toString()); + item.setActionCommand(getID()+CYCLECMDSEPERATOR+String.valueOf(i)); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + InstructionNodeGraphScene s=(InstructionNodeGraphScene)getScene(); + String com=e.getActionCommand(); + int index=com.indexOf(CYCLECMDSEPERATOR); + if(index!=-1){ + try{ + s.handleMakeCycleVisible(com.substring(0,index), + Integer.parseInt(com.substring(index+1))); + }catch(Exception ex){} + } + } + }); + menu.add(item); + i++; + } + } + popup.add(menu); + + //MENU Show Predecessors + menu=new JMenu(SHOWPREDECESSORS); + LinkedList list=new LinkedList(); + InstructionNodeGraphScene s=(InstructionNodeGraphScene)getScene(); + for(Instruction i:instruction.getPredecessors()){ + InstructionNodeWidget w=s.getNodeWidget(i.getID()); + if(w!=null && !w.isWidgetVisible()) list.add(w); + } + + if(list.size()>0){ + //Show all + item =new JMenuItem(ALL); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + InstructionNodeGraphScene scene=(InstructionNodeGraphScene)getScene(); + scene.handleShowNodes(getInstruction().getPredecessors()); + } + }); + menu.add(item); + + for(InstructionNodeWidget w: list){ + item =new JMenuItem(w.getID()); + item.setActionCommand(w.getID()); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + InstructionNodeGraphScene scene=(InstructionNodeGraphScene)getScene(); + InstructionNodeWidget nw=scene.getNodeWidget(e.getActionCommand()); + if(nw!=null){ + Instruction[] inst=new Instruction[1]; + inst[0]=nw.getInstruction(); + scene.handleShowNodes(inst); + } + } + }); + menu.add(item); + } + } + else menu.setEnabled(false); + popup.add(menu); + + + //MENU Show Successors + menu=new JMenu(SHOWSUCCESSORS); + list=new LinkedList(); + + for(Instruction i:instruction.getSuccessors()){ + InstructionNodeWidget w=s.getNodeWidget(i.getID()); + if(w!=null && !w.isWidgetVisible()) list.add(w); + } + + if(list.size()>0){ + //Show all + item =new JMenuItem(ALL); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + InstructionNodeGraphScene scene=(InstructionNodeGraphScene)getScene(); + scene.handleShowNodes(getInstruction().getSuccessors()); + } + }); + menu.add(item); + + for(InstructionNodeWidget w: list){ + item =new JMenuItem(w.getID()); + item.setActionCommand(w.getID()); + item.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + InstructionNodeGraphScene scene=(InstructionNodeGraphScene)getScene(); + InstructionNodeWidget nw=scene.getNodeWidget(e.getActionCommand()); + if(nw!=null){ + Instruction[] inst=new Instruction[1]; + inst[0]=nw.getInstruction(); + scene.handleShowNodes(inst); + } + } + }); + menu.add(item); + } + } + else menu.setEnabled(false); + popup.add(menu); + + boolean first=true; + //MENUITEMS added by contibutor attibutes + Iterator iter=attributeList.iterator(); + while(iter.hasNext()){ + INodeAttribute a=iter.next(); + if(a instanceof IPopupContributorAttribute && a.validate()) { + item=((IPopupContributorAttribute)a).getMenuItem(); + //seperator before the first element + if(first){ + popup.addSeparator(); + first=false; + } + popup.add(item); + } + } + } + + /** + * Returns if the node is expanded. + */ + public boolean isPathHighlighted(){ + return pathHighlighted; + } + + /** Returns the unique id of the node*/ + public String getID() { + return instruction.getID(); + } + + /** Sets the cycles the node is within*/ + public void setCycles(Collection cycles) { + this.cycles=cycles; + setToolTipText(createToolTip()); + } + + /** Returns a collection of all cycles the node is within */ + public Collection getCycleWidgets(){ + return cycles; + } + + /** Sets the cluster the node is within */ + public void setClusterWidget(ClusterWidget w){ + cluster=w; + } + + /** Returns the cluster */ + public ClusterWidget getClusterWidget(){ + return cluster; + } + + /** Creates a html tooltip for the instruction */ + protected String createToolTip(){ + StringBuffer ret=new StringBuffer(); + ret.append("Name: "+instruction.getID()+"

"); + ret.append("Instruction: "+instruction.getInstructionString()+"

"); + if(cycles!=null && cycles.size() >0){ + ret.append("Cycles:"); + for(InstructionNodeWidget[] a:cycles){ + ret.append("

* "); + for(InstructionNodeWidget w:a){ + ret.append(w.getID()); + ret.append(" "); + } + } + } + ret.append(""); + return ret.toString(); + } + + /** + * Returns the popup menu of the node + */ + public JPopupMenu getPopup(){ + rebuildPopupMenu(); + return popup; + } + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionSceneListener.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionSceneListener.java new file mode 100644 index 000000000000..ada8ea61f98e --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/InstructionSceneListener.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.graph; + +import java.util.Set; + +/** + * Listener interface for the Node Graph Scene. Listeners are notified on changes + * within the Scene. + * + * @author Stefan Loidl + */ +public interface InstructionSceneListener { + public void doubleClicked(InstructionNodeWidget w); + public void updateNodeData(); + public void selectionChanged(Set w); +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/SetLocationAnimator.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/SetLocationAnimator.java new file mode 100644 index 000000000000..76017c076403 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/graph/SetLocationAnimator.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.graph; + +import at.ssw.positionmanager.Vertex; +import at.ssw.positionmanager.impl.GraphVertex; +import java.awt.Point; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import org.netbeans.api.visual.animator.Animator; + +/** + * This Animator works fairly like the SetPreferredLocation Animator built within + * the visual library. The special difference is, that it handles more than one element + * per iteration. + * The reason why it is implemented are the cluster nodes which are calculated from the + * node positions have to be updated after each step. + * + * @author Stefan Loidl + */ +public class SetLocationAnimator extends Animator{ + + private LinkedList widgets; + private LinkedList startpoint; + private LinkedList targetpoint; + private InstructionNodeGraphScene scene; + + + /** + * VertexToWidget contains the Vertex with the target position, and the widget with the + * start position. + * It's essential that scene parameter is assigned- otherwise a nullpointer exception will occur! + */ + public SetLocationAnimator(InstructionNodeGraphScene scene, Hashtable VertexToWidget) { + super(scene.getSceneAnimator()); + + widgets= new LinkedList(); + startpoint= new LinkedList(); + targetpoint=new LinkedList(); + + if(VertexToWidget==null) return; + this.scene=scene; + + Point p1; + Point p2; + + + for(Map.Entry e: VertexToWidget.entrySet()){ + p1=e.getKey().getPosition(); + + if(p1==null) p1=new Point(); + //instruction widget inset correction: needed because used layouter do not support insets! + else p1.translate(InstructionNodeWidget.BORDERINSET,InstructionNodeWidget.BORDERINSET); + + p2=e.getValue().getPreferredLocation(); + if(p2==null) p2=new Point(); + + //Set vertex position to start position to avoid some line flickering + e.getKey().setPosition(p2); + + if(p1.x!=p2.x || p1.y!= p2.y){ + startpoint.add(p2); + targetpoint.add(p1); + widgets.add(e.getValue()); + } + //No Animation in this case- but dirty flags have to be set nevertheless! + else{ + Vertex v=e.getKey(); + if(v instanceof GraphVertex) ((GraphVertex)v).setDirty(true); + } + } + + } + + /** + * Starts the animation. + */ + public void animate(){ + start(); + } + + protected void tick(double d) { + Iterator w=widgets.iterator(); + Iterator start=startpoint.iterator(); + Iterator target=targetpoint.iterator(); + while(w.hasNext()){ + InstructionNodeWidget widget=w.next(); + Point p1=start.next(); + Point p2=target.next(); + Point p3; + + if(d>=1.0) p3=p2; + else p3=new Point ((int) (p1.x + d * (p2.x - p1.x)), + (int) (p1.y + d * (p2.y - p1.y))); + + widget.setPreferredLocation(p3); + //Update layoutGraph positions + Vertex v=scene.getWidgetToVertex().get(widget); + if(v!=null) v.setPosition(p3); + if(v instanceof GraphVertex) ((GraphVertex)v).setDirty(true); + } + + scene.refreshClusterWidgets(); + } + + + + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/instructions/Instruction.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/instructions/Instruction.java new file mode 100644 index 000000000000..0850d047668d --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/instructions/Instruction.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.instructions; + +import java.util.ArrayList; + + +/** + * An Instruction represents a HIR operation. It has predecessors which are + * the parameters of the function, and it has successors which are the + * usages of the result later on. The instructionString component is the + * string representation of the instruction behaviour. Each function is + * unique and therefore identified with an id. + * This class is not intended to be constructed outside of the package. + * + * @author Stefan Loidl + */ +public class Instruction { + + //unique identifier of the instruction + private String id; + + //unique identifier of the source block + private String sourceBlock; + + private String instructionString; + private ArrayList predecessors; + private ArrayList successors; + + private InstructionType type; + + + /** + * Internally used Constructor + */ + protected Instruction(String id){ + this.id=id; + predecessors=new ArrayList(); + successors=new ArrayList(); + instructionString=null; + sourceBlock=null; + type=InstructionType.UNKNOWN; + } + + /** + * Constructor + */ + protected Instruction(String id, String instruction, String source,Instruction[] pre, Instruction[] succ, InstructionType t) { + this.id=id; + this.instructionString=instruction; + + predecessors=new ArrayList(); + successors=new ArrayList(); + sourceBlock=source; + + if(pre!=null) + for(Instruction i : pre) predecessors.add(i); + if(succ!=null) + for(Instruction i :succ) successors.add(i); + type=t; + } + + /** + * As we are in single static assignment form two instructions are + * equal if their id is eqaul. + */ + public boolean equals(Object o){ + if(o instanceof Instruction) + return (((Instruction)o).getID().equals(id)); + return false; + } + + + // + /** + * Returns the id of the Instruction. + */ + public String getID(){ + return id; + } + + /** + * Sets the instruction string defining the behaviour + * of the instruction. + */ + protected void setInstructionString(String is){ + instructionString=is; + } + + /** + * Returns the string defining the instruction + */ + public String getInstructionString(){ + return instructionString; + } + + /** + * Returns the string identifying the source block + */ + public String getSourceBlock(){ + return sourceBlock; + } + + /** + * Sets the source Block + */ + protected void setSourceBlock(String s){ + sourceBlock=s; + } + + /** + * Sets the type of the Instruction + */ + protected void setInstructionType(InstructionType t){ + type=t; + } + + /** + * Returns the type of the Instruction + */ + public InstructionType getInstructionType(){ + return type; + } + // + + // + /** + * Returns an array with all predecessors + */ + public Instruction[] getPredecessors(){ + Instruction[] ret=new Instruction[predecessors.size()]; + ret=predecessors.toArray(ret); + return ret; + } + + /** + * Adds a Predecessor to the Instruction + */ + protected void addPredecessor(Instruction i){ + predecessors.add(i); + } + + /** + * Removes a Predecessor from the Instruction + */ + protected void removePredecessor(Instruction i){ + predecessors.remove(i); + } + + /** + * Returns an array with all successors + */ + public Instruction[] getSuccessors(){ + Instruction[] ret=new Instruction[successors.size()]; + ret=successors.toArray(ret); + return ret; + } + + /** + * Adds a successors to the Instruction + */ + protected void addSuccessors(Instruction i){ + successors.add(i); + } + + /** + * Removes a successors from the Instruction + */ + protected void removeSuccessors(Instruction i){ + successors.remove(i); + } + // + + public enum InstructionType{ + UNKNOWN, + CONSTANT, + PHI, + PARAMETER, + OPERATION, + CONTROLFLOW + } + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/instructions/InstructionSet.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/instructions/InstructionSet.java new file mode 100644 index 000000000000..87c256eb94d3 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/instructions/InstructionSet.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.instructions; + +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.LinkedList; + +/** + * This class can be seen as an infinite set of instructions. It contains + * a number of instructions, but if an instruction is queried that is not + * within the set, an instruction is added to the set and is returned. + * Using this behaviour a simple out of order construction of the instruction + * graph is possible. + * + * @author Stefan Loidl + */ +public class InstructionSet { + + private Hashtable instructions; + + /** + * Default Constructor + */ + public InstructionSet() { + instructions=new Hashtable(); + } + + /** + * Returns the instruction with id if its within the set, or a new + * instruction if not (the new instruction is then added to the set too) + */ + public Instruction getInstruction(String id){ + if(instructions.containsKey(id)) + return instructions.get(id); + else{ + Instruction i=new Instruction(id); + instructions.put(id,i); + return i; + } + } + + /** + * Simply adds a link between two instructions by adding + * prede- and successor to the corresponding elements. + */ + public boolean addLink(Instruction from,Instruction to){ + if(from!=null && to!= null){ + from.addSuccessors(to); + to.addPredecessor(from); + return true; + } + else return false; + } + + /** + * Returns an array of Instructions contained in the set, + * no assumptions about the ordering should be maid. + */ + public Instruction[] getInstructions(){ + Instruction[] inst=new Instruction[instructions.size()]; + inst=instructions.values().toArray(inst); + return inst; + } + + /** + * Removes all nodes of the specified type from the set. + * Currently no Control Flow Instrucitons are shown within the + * Graph. For this purpose this method is used. + */ + public static Instruction[] filterInstructionType(Instruction[] iset,Instruction.InstructionType type){ + LinkedList list=new LinkedList(); + + for(Instruction i : iset){ + if(i.getInstructionType()!=type) list.add(i); + } + + Instruction[] ret=new Instruction[list.size()]; + + return list.toArray(ret); + } + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/instructions/InstructionSetGenerator.java b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/instructions/InstructionSetGenerator.java new file mode 100644 index 000000000000..15e5c64c26cf --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/java/at/ssw/visualizer/dataflow/instructions/InstructionSetGenerator.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.instructions; + +import at.ssw.visualizer.model.cfg.*; +import java.util.ArrayList; +import java.util.List; + +/** + * This class is a static helper class to create a InstructionSet out of + * the BasicBlocks extraced from the ControlFlowGraph component. It is + * light weight to be easy to adopt to changes in the structure of the + * datasource (BasicBlock[]). + * + * @author Stefan Loidl + */ +public class InstructionSetGenerator { + + //characters that can occur as prefix for an operation + //source for this information: see at.ssw.visualizer.ir.model.IRScanner.isHir() + private static String idStart="ailfdv"; + + private static char idBlock='B'; + + /** + * Generates a InstructionSet from an array of BasicBlocks + */ + public static InstructionSet generateFromBlocks(List blocks){ + if(blocks==null) return null; + InstructionSet iset=new InstructionSet(); + Instruction instr1, instr2; + + for(BasicBlock b : blocks){ + if(!b.hasHir()) continue; + //Handle HIR Instructions for standard operations + for(IRInstruction hi:b.getHirInstructions()){ + + instr1= iset.getInstruction(hi.getValue(IRInstruction.HIR_NAME)); + instr1.setInstructionString(hi.getValue(IRInstruction.HIR_TEXT)); + instr1.setSourceBlock(b.getName()); + + if(containsBlockIdentifier(hi.getValue(IRInstruction.HIR_TEXT))) + instr1.setInstructionType(Instruction.InstructionType.CONTROLFLOW); + else{ + if(startsLikeConstant(hi.getValue(IRInstruction.HIR_TEXT))) instr1.setInstructionType(Instruction.InstructionType.CONSTANT); + else instr1.setInstructionType(Instruction.InstructionType.OPERATION); + } + + //Extract instructions from InstructionString + for(String s : parseInstructionString(hi.getValue(IRInstruction.HIR_TEXT))){ + instr2= iset.getInstruction(s); + if(instr1!=instr2) iset.addLink(instr2,instr1); + //If an instruction contains predecessors it's an operation + instr1.setInstructionType(Instruction.InstructionType.OPERATION); + } + } + + //Handle local state of the BasicBlock b + for(State state: b.getStates()){ + for(StateEntry stentry: state.getEntries()){ + + instr1= iset.getInstruction(stentry.getName()); + + //First occurance of local state var without Phi is decided to be + //a method param + if(instr1.getInstructionString()==null && !stentry.hasPhiOperands()){ + instr1.setInstructionString(""); + instr1.setInstructionType(Instruction.InstructionType.PARAMETER); + instr1.setSourceBlock(b.getName()); + } + + //Add Phi operands if some exist + if(stentry.hasPhiOperands()){ + instr1.setSourceBlock(b.getName()); + instr1.setInstructionType(Instruction.InstructionType.PHI); + String instrString="Phi["; + for(String s : stentry.getPhiOperands()){ + instr2=iset.getInstruction(s); + if(instr1!=instr2) iset.addLink(instr2,instr1); + instrString+=s+","; + } + instr1.setInstructionString(instrString.substring(0,instrString.length()-1)+"]"); + } + } + } + } + return iset; + } + + + /** + * Returns if an Blockidentifier is contained within the string. (Eg. B4, B56...) + */ + private static boolean containsBlockIdentifier(String iString){ + int pos=iString.indexOf(idBlock); + if(pos!=-1 && iString.length()>pos+1 && isNumber(iString.charAt(pos+1))) return true; + else return false; + } + + + /** + * This method extracts the identifiers from an instuction string. + * These are returned as a collection of strings. + */ + private static ArrayList parseInstructionString(String iString){ + //In most cases the Instruction string will hold two identifiers + ArrayList list=new ArrayList(2); + + if(iString!=null){ + int from=0, pos=0; + + while(pos='0' && c<='9'); + } + + /** + * Returns if char c is alphanumeric + */ + private static boolean isAlphaNumeric(char c){ + if(isNumber(c)) return true; + return (c>='a' && c<='z') || (c>='A' && c<='Z'); + } + + /** + * Returns if s starts like a constant: + * - "1){ + return isNumber(s.charAt(1)); + } + return false; + } + +} diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/DataFlowGraph/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..9208a5f1d4f1 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/nbm/manifest.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.dataflow.graph +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/dataflow/graph/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/DataFlowGraph/src/main/resources/at/ssw/visualizer/dataflow/graph/Bundle.properties b/visualizer/C1Visualizer/DataFlowGraph/src/main/resources/at/ssw/visualizer/dataflow/graph/Bundle.properties new file mode 100644 index 000000000000..df360d41cddc --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowGraph/src/main/resources/at/ssw/visualizer/dataflow/graph/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Data Flow Graph diff --git a/visualizer/C1Visualizer/DataFlowView/pom.xml b/visualizer/C1Visualizer/DataFlowView/pom.xml new file mode 100644 index 000000000000..5e245ebe7fa1 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowView/pom.xml @@ -0,0 +1,97 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + DataFlowView + 1.13-SNAPSHOT + nbm + DataFlowView + + UTF-8 + + + + at.ssw.visualizer + VisualizerUI + ${project.version} + + + at.ssw.visualizer + DataFlowGraph + ${project.version} + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + org.netbeans.api + org-netbeans-api-visual + ${netbeans.version} + + + org.netbeans.api + org-openide-nodes + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.DataFlowView + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/DataFlowView/src/main/java/at/ssw/visualizer/dataflow/view/DataflowTableModel.java b/visualizer/C1Visualizer/DataFlowView/src/main/java/at/ssw/visualizer/dataflow/view/DataflowTableModel.java new file mode 100644 index 000000000000..965d2bd93810 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowView/src/main/java/at/ssw/visualizer/dataflow/view/DataflowTableModel.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.view; + +import at.ssw.visualizer.dataflow.graph.ClusterWidget; +import at.ssw.visualizer.dataflow.graph.InstructionNodeGraphScene; +import at.ssw.visualizer.dataflow.graph.InstructionNodeWidget; +import at.ssw.visualizer.dataflow.instructions.Instruction; +import java.util.Arrays; +import java.util.Comparator; +import javax.swing.table.AbstractTableModel; + +/** + * + * @author Stefan Loidl + * @author Christian Wimmer + */ +public class DataflowTableModel extends AbstractTableModel { + public static final int COLUMN_VISIBLE = 0; + public static final int COLUMN_NAME = 1; + public static final int COLUMN_BLOCK = 2; + public static final int COLUMN_TYPE = 3; + public static final int COLUMN_STATEMENT = 4; + public static final int COLUMN_SUCC = 5; + public static final int COLUMN_PRED = 6; + public static final int COLUMN_CLUSTER = 7; + + public static final String[] COLUMN_NAMES = {"Visible", "Name", "Block", "Type", "Statement", "Successors", "Predecessors", "Cluster"}; + public static final int[] COLUMN_WIDTHS = {60, 60, 60, 60, 200, 120, 120, 60}; + + private InstructionNodeWidget[] widgets = null; + + /** Sets the data source for the tablemodel*/ + public void setDataSource(InstructionNodeGraphScene scene) { + if (scene == null) { + widgets = null; + } else { + widgets = scene.getNodeWidgets(); + } + fireTableDataChanged(); + } + + + /** Sorts the data source, according to the value within specified column */ + public void sort(final int column) { + if (widgets == null) { + return; + } + Arrays.sort(widgets, new Comparator() { + public int compare(InstructionNodeWidget w1, InstructionNodeWidget w2) { + String s1 = getValue(w1, column).toString(); + String s2 = getValue(w2, column).toString(); + if (column == COLUMN_NAME || column == COLUMN_BLOCK || column == COLUMN_CLUSTER) { + if (s1.length() > 1 && s2.length() > 1) { + s1 = s1.substring(1); + s2 = s2.substring(1); + try { + int i1 = Integer.valueOf(s1); + int i2 = Integer.valueOf(s2); + return i1 - i2; + } catch (Exception e) { + } + } + } + return s1.compareTo(s2); + } + }); + fireTableDataChanged(); + } + + + public int getRowCount() { + return widgets == null ? 0 : widgets.length; + } + + public int getColumnCount() { + return COLUMN_NAMES.length; + } + + @Override + public String getColumnName(int column) { + return COLUMN_NAMES[column]; + } + + public Object getValueAt(int row, int column) { + return getValue(widgets[row], column); + } + + protected Object getValue(InstructionNodeWidget nw, int column) { + Instruction inst = nw.getInstruction(); + ClusterWidget cw = nw.getClusterWidget(); + if (inst == null) { + return ""; + } + + switch (column) { + case COLUMN_VISIBLE: + return nw.isWidgetVisible(); + case COLUMN_NAME: + return nw.getID(); + case COLUMN_BLOCK: + return inst.getSourceBlock(); + case COLUMN_TYPE: + return getTypeString(inst.getInstructionType()); + case COLUMN_STATEMENT: + return inst.getInstructionString(); + case COLUMN_SUCC: + return getIDString(inst.getSuccessors()); + case COLUMN_PRED: + return getIDString(inst.getPredecessors()); + case COLUMN_CLUSTER: + return cw == null ? "" : cw.getId(); + default: + throw new Error("invalid column"); + } + } + + private String getTypeString(Instruction.InstructionType type) { + switch (type) { + case CONSTANT: + return "constant"; + case CONTROLFLOW: + return "control flow"; + case OPERATION: + return "operation"; + case PARAMETER: + return "parameter"; + case PHI: + return "phi"; + default: + return "undefined"; + } + } + + private String getIDString(Instruction[] instructions) { + if (instructions == null) { + return ""; + } + + StringBuilder sb = new StringBuilder(); + for (Instruction instruction : instructions) { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(instruction.getID()); + } + return sb.toString(); + } + + /** Returns a Widget for the given row */ + public InstructionNodeWidget getWidgetAtRow(int row) { + if (widgets != null && row < widgets.length) { + return widgets[row]; + } else { + return null; + } + } + + /** Sets the value of a cell- only visible state can be changed */ + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + if (columnIndex == COLUMN_VISIBLE && widgets != null && rowIndex < widgets.length) { + InstructionNodeGraphScene s = (InstructionNodeGraphScene) widgets[rowIndex].getScene(); + s.handleToggleVisibility(widgets[rowIndex].getInstruction(), false); + } + } + + @Override + public Class getColumnClass(int column) { + if (column == COLUMN_VISIBLE) { + return Boolean.class; + } + return String.class; + } + + /** Only visible state is editable */ + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return columnIndex == COLUMN_VISIBLE; + } +} diff --git a/visualizer/C1Visualizer/DataFlowView/src/main/java/at/ssw/visualizer/dataflow/view/DataflowViewTopComponent.java b/visualizer/C1Visualizer/DataFlowView/src/main/java/at/ssw/visualizer/dataflow/view/DataflowViewTopComponent.java new file mode 100644 index 000000000000..87a7b41376f3 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowView/src/main/java/at/ssw/visualizer/dataflow/view/DataflowViewTopComponent.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.view; + +import at.ssw.visualizer.core.selection.Selection; +import at.ssw.visualizer.core.selection.SelectionManager; +import at.ssw.visualizer.dataflow.graph.InstructionNodeGraphScene; +import at.ssw.visualizer.dataflow.graph.InstructionNodeWidget; +import at.ssw.visualizer.dataflow.graph.InstructionSceneListener; +import java.awt.BorderLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.io.Serializable; +import java.util.Set; +import javax.swing.BorderFactory; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; + +/** + * Top component which displays something. + * + * @author Stefan Loidl + * @author Christian Wimmer + */ +public final class DataflowViewTopComponent extends TopComponent { + private InstructionNodeGraphScene curScene; + + private JTable nodeTable; + private DataflowTableModel tableModel; + + private DataflowViewTopComponent() { + setName("Data Flow"); + setToolTipText("Data Flow"); + + tableModel = new DataflowTableModel(); + nodeTable = new javax.swing.JTable(tableModel); + nodeTable.setRowMargin(0); + nodeTable.getColumnModel().setColumnMargin(0); + nodeTable.setShowGrid(false); + nodeTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + for (int i = 0; i < DataflowTableModel.COLUMN_WIDTHS.length; i++) { + nodeTable.getColumnModel().getColumn(i).setPreferredWidth(DataflowTableModel.COLUMN_WIDTHS[i]); + } + nodeTable.addMouseListener(tableMouseListener); + nodeTable.getTableHeader().addMouseListener(headerMouseListener); + + JScrollPane scrollPane = new JScrollPane(nodeTable); + scrollPane.setViewportBorder(BorderFactory.createEmptyBorder()); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + setLayout(new BorderLayout()); + add(scrollPane); + } + + @Override + protected void componentShowing() { + super.componentShowing(); + SelectionManager.getDefault().addChangeListener(selectionChangeListener); + updateContent(); + } + + @Override + protected void componentHidden() { + super.componentHidden(); + SelectionManager.getDefault().removeChangeListener(selectionChangeListener); + if (curScene != null) { + curScene.removeInstructionSceneListener(instructionSceneListener); + } + curScene = null; + tableModel.setDataSource(null); + } + + + private MouseListener tableMouseListener = new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (event.getButton() == MouseEvent.BUTTON1 && event.getClickCount() == 2) { + // Selects the node in the scene on doubleclick of an item + String s = nodeTable.getValueAt(nodeTable.getSelectedRow(), DataflowTableModel.COLUMN_NAME).toString(); + if (curScene != null) { + curScene.setSingleSelectedWidget(s, true); + } + } else if (event.getButton() == MouseEvent.BUTTON3 && event.getClickCount() == 1) { + // Show popup menu of the node in table + InstructionNodeWidget w = tableModel.getWidgetAtRow(nodeTable.rowAtPoint(event.getPoint())); + if (w != null) { + JPopupMenu pop = w.getPopup(); + if (pop != null) { + pop.show(nodeTable, event.getX(), event.getY()); + } + } + } + } + }; + + private MouseListener headerMouseListener = new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + tableModel.sort(nodeTable.columnAtPoint(event.getPoint())); + } + }; + + + private ChangeListener selectionChangeListener = new ChangeListener() { + public void stateChanged(ChangeEvent event) { + updateContent(); + } + }; + + private void updateContent() { + Selection selection = SelectionManager.getDefault().getCurSelection(); + InstructionNodeGraphScene newScene = selection.get(InstructionNodeGraphScene.class); + + if (newScene != curScene) { + if (curScene != null) { + curScene.removeInstructionSceneListener(instructionSceneListener); + } + tableModel.setDataSource(newScene); + if (newScene != null) { + newScene.addInstructionSceneListener(instructionSceneListener); + } + curScene = newScene; + } + } + + private InstructionSceneListener instructionSceneListener = new InstructionSceneListener() { + public void doubleClicked(InstructionNodeWidget w) { + if (w == null) { + return; + } + String id = w.getID(); + + for (int i = 0; i < nodeTable.getRowCount(); i++) { + if (nodeTable.getValueAt(i, DataflowTableModel.COLUMN_NAME).equals(id)) { + nodeTable.changeSelection(i, 0, false, false); + break; + } + } + } + + /** Data in InstructionNodeScene changed */ + public void updateNodeData() { + nodeTable.repaint(); + } + + /** Selection in editor has changes */ + public void selectionChanged(Set w) { + } + }; + + // + private static final String PREFERRED_ID = "DataflowViewTopComponent"; + private static DataflowViewTopComponent instance; + + public static synchronized DataflowViewTopComponent getDefault() { + if (instance == null) { + instance = new DataflowViewTopComponent(); + } + return instance; + } + + public static synchronized DataflowViewTopComponent findInstance() { + return (DataflowViewTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID); + } + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_ALWAYS; + } + + @Override + protected String preferredID() { + return PREFERRED_ID; + } + + @Override + public Object writeReplace() { + return new ResolvableHelper(); + } + + static final class ResolvableHelper implements Serializable { + private static final long serialVersionUID = 1L; + public Object readResolve() { + return DataflowViewTopComponent.getDefault(); + } + } + // +} diff --git a/visualizer/C1Visualizer/DataFlowView/src/main/java/at/ssw/visualizer/dataflow/view/ShowDataflowViewAction.java b/visualizer/C1Visualizer/DataFlowView/src/main/java/at/ssw/visualizer/dataflow/view/ShowDataflowViewAction.java new file mode 100644 index 000000000000..8020be67a110 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowView/src/main/java/at/ssw/visualizer/dataflow/view/ShowDataflowViewAction.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.dataflow.view; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.openide.windows.TopComponent; + +/** + * Action which shows DataflowView component. + * + * @author Stefan Loidl + */ +public class ShowDataflowViewAction extends AbstractAction { + public ShowDataflowViewAction() { + super("Data Flow View"); + } + + public void actionPerformed(ActionEvent event) { + TopComponent win = DataflowViewTopComponent.findInstance(); + win.open(); + win.requestActive(); + } +} diff --git a/visualizer/C1Visualizer/DataFlowView/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/DataFlowView/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..4aac83e37c2c --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowView/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.dataflow.view +OpenIDE-Module-Layer: at/ssw/visualizer/dataflow/view/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/dataflow/view/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/Bundle.properties b/visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/Bundle.properties new file mode 100644 index 000000000000..986c876559d1 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Data Flow View diff --git a/visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/DataflowViewTopComponentSettings.xml b/visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/DataflowViewTopComponentSettings.xml new file mode 100644 index 000000000000..0482c9eda778 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/DataflowViewTopComponentSettings.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/DataflowViewTopComponentWstcref.xml b/visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/DataflowViewTopComponentWstcref.xml new file mode 100644 index 000000000000..f1e1d33748fa --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/DataflowViewTopComponentWstcref.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/layer.xml b/visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/layer.xml new file mode 100644 index 000000000000..8bec860c49a7 --- /dev/null +++ b/visualizer/C1Visualizer/DataFlowView/src/main/resources/at/ssw/visualizer/dataflow/view/layer.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/GraphHelper/pom.xml b/visualizer/C1Visualizer/GraphHelper/pom.xml new file mode 100644 index 000000000000..a787d0045a87 --- /dev/null +++ b/visualizer/C1Visualizer/GraphHelper/pom.xml @@ -0,0 +1,57 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + GraphHelper + 1.13-SNAPSHOT + nbm + GraphHelper + + UTF-8 + + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.graphhelper + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Block.java b/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Block.java new file mode 100644 index 000000000000..1fdb761177a6 --- /dev/null +++ b/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Block.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.graphhelper; + +import java.util.LinkedList; + +/** + * A Block is a connected component. This class is used during the planarity check of a + * graph. + * + * @author Stefan Loidl + */ +public class Block { + + LinkedList Latt, Ratt; + LinkedList Lseg, Rseg; + + /** Creates a new instance of Block */ + public Block(Edge e, LinkedList A) { + Latt=new LinkedList(); + Ratt=new LinkedList(); + Lseg=new LinkedList(); + Rseg=new LinkedList(); + + Lseg.add(e); + Latt= new LinkedList(A); + A.clear(); + } + + /** Interchanges the two sides of the block*/ + public void flip(){ + LinkedList ha; + LinkedList he; + + ha=Ratt; Ratt=Latt; Latt=ha; + he=Rseg; Rseg=Lseg; Lseg=he; + } + + public Integer headOfLatt() { + return Latt.getFirst(); + } + + public boolean emptyLatt(){ + return Latt.isEmpty(); + } + + public Integer headOfRatt() { + return Ratt.getFirst(); + } + + public boolean emptyRatt(){ + return Ratt.isEmpty(); + } + + /** check for interlacing with the left side of the topmost block of S*/ + public boolean leftInterlace(LinkedList S){ + assert !Latt.isEmpty(); + + if(!S.isEmpty() && !S.getFirst().emptyLatt() && Latt.getLast().intValue() < S.getFirst().headOfLatt().intValue()) + return true; + else return false; + } + + /** check for interlacing with the right side of the topmost block of S*/ + public boolean rightInterlace(LinkedList S){ + assert !Latt.isEmpty(); + + if(!S.isEmpty() && !S.getFirst().emptyRatt() && Latt.getLast().intValue() < S.getFirst().headOfRatt().intValue()) + return true; + else return false; + } + + /** Add block Bprime to the rear of this block*/ + public void combine(Block Bprime){ + Latt.addAll(Bprime.Latt); + Ratt.addAll(Bprime.Ratt); + Lseg.addAll(Bprime.Lseg); + Rseg.addAll(Bprime.Rseg); + } + + /** Remove all attachments to w; there may be several */ + public boolean clean(int dfsNumW){ + while(!Latt.isEmpty() && Latt.getFirst().intValue()==dfsNumW) Latt.removeFirst(); + while(!Ratt.isEmpty() && Ratt.getFirst().intValue()==dfsNumW) Ratt.removeFirst(); + + if(!Latt.isEmpty() || !Ratt.isEmpty()) return false; + + // If Latt and Ratt are empty we reorder the placement of the subsegments in alpha + for(Edge e:Lseg) ((DiGraph.PlanarityEdgePayload)e.data).alpha=DiGraph.LEFT; + for(Edge e:Rseg) ((DiGraph.PlanarityEdgePayload)e.data).alpha=DiGraph.RIGHT; + + return true; + } + + /** Add a Block to the rear of Att. Flip if necessary*/ + public void addToAtt(LinkedList Att, int dfsnumW0){ + if(!Ratt.isEmpty() && headOfRatt().intValue() > dfsnumW0) flip(); + Att.addAll(Latt); + Latt.clear(); + Att.addAll(Ratt); + Ratt.clear(); + + //Ratt is either empty or {w0}. Also if Ratt is non-empty then all subsequent sets are + //contained in {w0}. So we indeed compute an ordered set of attachments. + for(Edge e:Lseg) ((DiGraph.PlanarityEdgePayload)e.data).alpha=DiGraph.LEFT; + for(Edge e:Rseg) ((DiGraph.PlanarityEdgePayload)e.data).alpha=DiGraph.RIGHT; + } + + + +} diff --git a/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/DiGraph.java b/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/DiGraph.java new file mode 100644 index 000000000000..5965b7c7b700 --- /dev/null +++ b/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/DiGraph.java @@ -0,0 +1,803 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.graphhelper; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Hashtable; +import java.util.LinkedList; + +/** + * Implements a Directed Graph. Some graph theoretic features like filtering + * connected components and making a graph biconnected are supported. + * Furthermore a planarity testing algorithm is implemented as described in: + * + * Kurt Mehlhorn, Petra Mutzel, Stefan Naeher: An Implementation of the Hopcroft + * and Trajan Planarity Test and Embedding Algorithm. Technical Report, 1993. + * MPI-I-93-151 + * + * @author Stefan Loidl + */ +public class DiGraph { + + protected Hashtable nodes; + protected LinkedList edges; + + //This field is used within makeBiConnected Algorithm. + private int dfsCount=0; + + //Used during planarity testing + final static int LEFT=1; + final static int RIGHT=2; + + //during the panarity-test step this variable + //is assigned to be the biconnected component + //which is used within the embedding step. + private DiGraph BiConnected=null; + + //Used in embedding algorithm + private int cur_nr=0; + + + /** Creates a new instance of DiGraph */ + public DiGraph() { + nodes=new Hashtable(); + edges=new LinkedList(); + } + + /** Adds a new Node to the DiGraph*/ + public void addNode(Node n){ + nodes.put(n.ID,n); + } + + /** Removes a Node from the DiGraph*/ + public void removeNode(Node n){ + for(Edge e : n.edges.toArray(new Edge[n.edges.size()])) + removeEdge(e); + + nodes.remove(n.ID); + } + + /** Returns a Node object with specified id*/ + public Node getNode(String id){ + return nodes.get(id); + } + + /** Returns if a node with specified id is contained*/ + public boolean contains(String id){ + return nodes.containsKey(id); + } + + /** Adds a new edge to the DiGraph*/ + public void addEdge(Edge e){ + e.source.succ.add(e.destination); + e.destination.pred.add(e.source); + edges.add(e); + e.source.edges.add(e); + e.destination.edges.add(e); + } + + /** Removes an edge for the DiGraph*/ + public void removeEdge(Edge e) { + if(e==null) return; + edges.remove(e); + e.source.succ.remove(e.destination); + e.destination.pred.remove(e.source); + e.source.edges.remove(e); + e.destination.edges.remove(e); + } + + /** Returns a Collection of all Nodes*/ + public Collection getNodes(){ + return nodes.values(); + } + + /** Returns a Collection of all Edges*/ + public Collection getEdges(){ + return edges; + } + + /** Returns the Edge object with source and destination given*/ + protected Edge getEdge(Node source, Node dest) { + for(Edge e: edges){ + if(e.source==source && e.destination==dest) return e; + } + return null; + } + + /** Resets the flags of all Nodes*/ + public void resetNodeFlags(){ + for(Node n:nodes.values()){ + n.visited=false; + } + } + + /** + * Returns a clone of the current DiGraph + */ + public DiGraph clone(){ + DiGraph c=new DiGraph(); + + for(Node n: nodes.values()) c.addNode(new Node(n.ID)); + + for(Edge e: edges) + c.addEdge(new Edge(c.getNode(e.source.ID),c.getNode(e.destination.ID))); + + return c; + } + + /** + * Makes the Graph bidirected by adding Edges + */ + public void makeBiDirected(){ + for(Node n: nodes.values()){ + for(Node s:n.succ){ + if(!n.pred.contains(s)) addEdge(new Edge(s,n)); + } + for(Node p:n.pred){ + if(!n.succ.contains(p)) addEdge(new Edge(n,p)); + } + } + } + + /** + * Breaks all bidirected Edges by deleting one of + * them. + */ + public void breakBiDirection(){ + LinkedList del=new LinkedList(); + for(Node n: nodes.values()){ + for(Node p:n.pred){ + if(n.succ.contains(p)){ + Edge ed=getEdge(n,p); + if(!del.contains(ed))del.add(getEdge(p,n)); + } + } + } + for(Edge e:del){ + removeEdge(e); + } + } + + /** + * Returns a list of DiGraphs containing the connected components of + * the current DiGraph- Nodes are cloned! + */ + public Collection getConnectedComponents(){ + LinkedList list=new LinkedList(); + resetNodeFlags(); + for(Node n:nodes.values()){ + if(!n.visited){ + list.add(findConnctedComponent(n)); + } + } + + return list; + } + + /** + * Extracts the connected component from Node n. + */ + private DiGraph findConnctedComponent(Node n) { + DiGraph dg=new DiGraph(); + findConnctedComponent_rek(n,dg); + + for(Edge e: getEdges()){ + if(dg.contains(e.source.ID)){ + dg.addEdge(new Edge(dg.getNode(e.source.ID),dg.getNode(e.destination.ID))); + } + } + + return dg; + } + + private void findConnctedComponent_rek(Node n, DiGraph dg) { + if(n.visited) return; + n.visited=true; + + dg.addNode(new Node(n.ID)); + + for(Node node:n.succ){ + findConnctedComponent_rek(node,dg); + } + for(Node node:n.pred){ + findConnctedComponent_rek(node,dg); + } + } + + /** + * Returns a collecation of arrays of circualar Nodes + */ + public Collection getCircularDependency(Node n){ + LinkedList ret=new LinkedList(); + LinkedList current=new LinkedList(); + + resetNodeFlags(); + + n.visited=true; + current.add(n); + for(Node s:n.succ){ + getCircularDependency_rec(n ,s, current, ret); + } + + + return ret; + } + + /** + * Recusive helper- searching cyclic structures. + */ + private void getCircularDependency_rec(Node source ,Node node, LinkedList current, LinkedList result){ + if(node.visited) return; + node.visited=true; + + current.addLast(node); + + for(Node n:node.succ){ + //Cycle found! + if(n==source){ + result.add(current.toArray(new Node[current.size()])); + } + //Another unvisited node + else if(!n.visited){ + getCircularDependency_rec(source,n,current,result); + } + } + + node.visited=false; + current.removeLast(); + } + + + /** + * This method makes the current graph biconnected by adding + * edges. It is assumed that the graph is connected & bidirected! + * The data field of all nodes is destroyed! + */ + public void makeBiConnected(){ + //distribute payload to all nodes + for(Node n: nodes.values()) n.data=new BiConPayload(); + + dfsCount=0; + + if(nodes.size()>0) + dfsInMakeBiConnected(nodes.values().iterator().next()); + + } + + /** + * This method searches for nodes connecting two subgraphs. + * To satisfy BiConnection another Edge is added to bridge that + * single node. This doesn't influence planarity of the graph. + */ + private void dfsInMakeBiConnected(Node n) { + Node u; + BiConPayload pl=(BiConPayload)n.data; + + pl.dfsNum=dfsCount++; + pl.lowPt=pl.dfsNum; + pl.reached=true; + + if(n.succ.size()==0) return; + + //get first child + u=n.succ.getFirst(); + + //Within the loop changes may happen -> successors. + //these do not have to be traversed. + Node[] na=n.succ.toArray(new Node[n.succ.size()]); + + //traverse all children + for(Node w: na){ + BiConPayload plw=(BiConPayload)w.data; + + //Edge (n->w) is a tree edge + if(!plw.reached){ + plw.parent=n; + dfsInMakeBiConnected(w); + + //Node was found -> a bridging edge has to + //be added. + if(plw.lowPt==pl.dfsNum){ + //if w is the first child and n has a parent + //we simply add a edge between them (bidirected!) + //This may not be done between other than the first + //Node because in the case planerity would be lost! (no face!) + if(w==u && pl.parent!=null){ + Edge e=new Edge(w,pl.parent); + addEdge(e); + addEdge(e.getReverseEdge()); + } + //else we simply add a bidirected Edge between the first child and the + //current child + if(u!=w){ + Edge e=new Edge(w,u); + addEdge(e); + addEdge(e.getReverseEdge()); + } + } + + //lowPt of our node n is minimum of lowPts + if(pl.lowPt > plw.lowPt) pl.lowPt=plw.lowPt; + } + //Edge (n->w) is not a tree edge + else{ + if(pl.lowPt > plw.dfsNum) pl.lowPt=plw.dfsNum; + } + } //for + } + + + + /** Overrides the toString for debug reasons only.*/ + public String toString(){ + String ret="Nodes: "; + for(Node n:nodes.values()){ + ret+=n.ID; + if(n.data!=null) ret+="("+n.data+")"; + ret+=", "; + } + + ret+="\nEdges: "; + for(Edge e: edges) + ret+="("+e.source.ID+"->"+e.destination.ID+") "; + + return ret; + } + + /** Tests if the current graph is planar*/ + public boolean isPlanar(){ + boolean ret=true; + for(DiGraph dg : getConnectedComponents()){ + ret= ret && dg.planar(); + } + return ret; + } + + /** + * Tests for the planarity of the graph destroying its structure! + * The graph is assumed to be connected. + */ + protected boolean planar(){ + int num=nodes.size(); + if(num <= 3) return true; + if(edges.size() > 6*num-12) return false; + + makeBiDirected(); + makeBiConnected(); + BiConnected=clone(); + + //Planarity Test + for(Node n: nodes.values()) n.data=new PlanarityNodePayload(); + for(Edge e: edges) e.data=new PlanarityEdgePayload(); + + reorder(); + LinkedList Att=new LinkedList(); + + if(nodes.size()==0) return true; + Node first=nodes.values().iterator().next(); + + assert first.succ.size()>0; + Edge firstEdge=getEdge(first,first.succ.getFirst()); + + ((PlanarityEdgePayload)firstEdge.data).alpha=LEFT; + + if(!stronglyPlanar(firstEdge,Att)) return false; + + return true; + } + + + private boolean stronglyPlanar(Edge e0,LinkedList Att){ + //determine the cycle C(e0) + Node x=e0.source; + Node y=e0.destination; + //get first adiacent edge to y + Edge e= getEdge(y,y.succ.getFirst()); + + Node wk=y; + + //while is a tree edge + while(((PlanarityNodePayload)e.destination.data).dfsNum > ((PlanarityNodePayload)wk.data).dfsNum){ + wk=e.destination; + e=getEdge(wk,wk.succ.getFirst()); + } + + Node w0=e.destination; + + //Process all edges leaving the spine of S(e0) + Node w=wk; + LinkedList S=new LinkedList(); + + while(w!=x){ + int count=0; + for(Node r:w.succ){ + e=getEdge(w,r); + count++; + if(count!=1){ + //Test Recursivly + LinkedList A=new LinkedList(); + //if: tree edge + if(((PlanarityNodePayload)w.data).dfsNum < ((PlanarityNodePayload)e.destination.data).dfsNum){ + if(!stronglyPlanar(e,A)) return false; + } + //else: back edge + else A.add(new Integer(((PlanarityNodePayload)e.destination.data).dfsNum)); + + //update stack S + Block B=new Block(e,A); + while(true){ + if(B.leftInterlace(S)) S.getFirst().flip(); + if(B.leftInterlace(S)) return false; + if(B.rightInterlace(S)) B.combine(S.poll()); + else break; + } + S.addFirst(B); + } + } + + //Prepare for next iteration + while(!S.isEmpty() && S.getFirst().clean(((PlanarityNodePayload)((PlanarityNodePayload)w.data).parent.data).dfsNum)) + S.removeFirst(); + + w=((PlanarityNodePayload)w.data).parent; + } + + + //test strong planerity and compute Att + Att.clear(); + while(!S.isEmpty()){ + Block B=S.poll(); + if(!B.emptyLatt() && !B.emptyRatt() && B.headOfLatt().intValue() > ((PlanarityNodePayload)w0.data).dfsNum + && B.headOfRatt().intValue() > ((PlanarityNodePayload)w0.data).dfsNum){ + return false; + } + + B.addToAtt(Att,((PlanarityNodePayload)w0.data).dfsNum); + } + + //w0 is an attachment of S(e0) except if w0 = x + if(w0 != x) Att.add(new Integer(((PlanarityNodePayload)w0.data).dfsNum)); + + return true; + } + + + /** + * This is a substep in planarity test algorithm- All data fields of + * nodes and edges are lost during this step! + */ + private void reorder(){ + if(nodes.size()==0) return; + + LinkedList deledges=new LinkedList(); + dfsCount=0; + + dfsInReorder(nodes.values().iterator().next(),deledges); + + for(Edge e: deledges) removeEdge(e); + + for(Edge e: edges){ + PlanarityNodePayload source=(PlanarityNodePayload)e.source.data; + PlanarityNodePayload dest=(PlanarityNodePayload)e.destination.data; + + PlanarityEdgePayload epl=(PlanarityEdgePayload)e.data; + + if(dest.dfsNum < source.dfsNum){ + epl.cost=2*dest.dfsNum; + } else{ + if(dest.lowpt2 >= source.dfsNum) epl.cost=2*dest.lowpt1; + else epl.cost=2*dest.lowpt1+1; + } + } + + sortEdges(); + } + + private void dfsInReorder(Node v, LinkedList deledges) { + PlanarityNodePayload vpl=(PlanarityNodePayload)v.data; + + vpl.dfsNum=dfsCount++; + vpl.lowpt1=vpl.lowpt2=vpl.dfsNum; + vpl.reached=true; + + //First pass + for(Node w: v.succ){ + PlanarityNodePayload wpl=(PlanarityNodePayload)w.data; + + //The edge (v -> w) is a tree edge + if(!wpl.reached){ + wpl.parent=v; + dfsInReorder(w,deledges); + vpl.lowpt1=Min(wpl.lowpt1,vpl.lowpt1); + } + else{ + vpl.lowpt1=Min(wpl.dfsNum,vpl.lowpt1); + + //the edge (v -> w) is a forward edge or the reversal of a tree edge. + if((wpl.dfsNum >= vpl.dfsNum) || w==vpl.parent) + deledges.add(getEdge(v,w)); + } + } + + + //Second pass + for(Node w: v.succ){ + PlanarityNodePayload wpl=(PlanarityNodePayload)w.data; + + //tree edge (assigned during first pass) + if(wpl.parent==v){ + if(wpl.lowpt1!=vpl.lowpt1) vpl.lowpt2=Min(wpl.lowpt1,vpl.lowpt2); + vpl.lowpt2=Min(vpl.lowpt2, wpl.lowpt2); + } + else{ + if(vpl.lowpt1 != wpl.dfsNum) vpl.lowpt2=Min(vpl.lowpt2, wpl.dfsNum); + } + } + + } + + /** Simple helper function returning the minimum*/ + private int Min(int a, int b) { + if(a>b) return b; + else return a; + } + + /** + * Used in reordering process of the planarity test. Edges are sorted + * according to their cost value. + */ + private void sortEdges() { + Edge[] e=edges.toArray(new Edge[edges.size()]); + + Arrays.sort(e,new Comparator(){ + public int compare(Edge e1, Edge e2) { + if(e1==null || e2==null || + e1.data==null || e2.data==null|| + !(e1.data instanceof PlanarityEdgePayload) || + !(e2.data instanceof PlanarityEdgePayload) ) return 0; + + return ((PlanarityEdgePayload)e1.data).cost- ((PlanarityEdgePayload)e2.data).cost; + } + }); + + //Delete all edges + for(Edge edge:e){ + removeEdge(edge); + } + + //Insert all edges is correct order + for(Edge edge:e){ + addEdge(edge); + } + } + + /** + * Assuming that the Graph is connected the method + * tests the planarity of the graph and constructs an + * embedding if it is planar. + */ + public Embedding createEmbedding(){ + + DiGraph G=clone(); + //Planar- algorithm makes shure the correct + //payload types are in the nodes. + if(G.planar()){ + if(G.nodes.size()<4) return new Embedding(clone()); + + DiGraph H=G.BiConnected; + for(Edge e :H.edges) e.data=new EmbeddingEdgePayload(); + //Lists of Edges of H + LinkedList T=new LinkedList(); + LinkedList A=new LinkedList(); + + cur_nr=0; + + Node first=G.nodes.values().iterator().next(); + assert first.succ.size()>0; + Edge firstEdge=G.getEdge(first,first.succ.getFirst()); + + + + G.resetNodeFlags(); + + embedding(firstEdge,G,H,LEFT,T,A); + + //conc R and A + T.addAll(A); + + for(Edge e:T) ((EmbeddingEdgePayload)e.data).sortNum=cur_nr++; + + //PlanarityPayload is used because cost can be used for + //sorting purposes. + for(Edge e:edges) { + PlanarityEdgePayload pl=new PlanarityEdgePayload(); + pl.cost=((EmbeddingEdgePayload)H.getEdge(H.getNode(e.source.ID),H.getNode(e.destination.ID)).data).sortNum; + e.data=pl; + } + + sortEdges(); + + return new Embedding(clone()); + } + return null; + } + + private void embedding(Edge e0, DiGraph G,DiGraph H, int t, LinkedList T, LinkedList A) { + //Determine Cycle C(e0) + Node x=e0.source; + Node y=e0.destination; + + ((PlanarityNodePayload)y.data).treeEdgeInto=e0; + Edge e1; + //first adjacent edge + e1=G.getEdge(y,y.succ.getFirst()); + Node wk=y; + + //e is a tree edge? + while(((PlanarityNodePayload)e1.destination.data).dfsNum > ((PlanarityNodePayload)wk.data).dfsNum){ + wk=e1.destination; + + ((PlanarityNodePayload)wk.data).treeEdgeInto=e1; + e1=G.getEdge(wk,wk.succ.getFirst()); + } + + Node w0=e1.destination; + Edge back_edge_into_w0=e1; + + //Process the subsegments + Node w=wk; + LinkedList Al=new LinkedList(); + LinkedList Ar=new LinkedList(); + LinkedList Tprime=new LinkedList(); + LinkedList Aprime=new LinkedList(); + + T.clear(); + T.add(H.getEdge(H.getNode(e1.source.ID),H.getNode(e1.destination.ID))); + while(w!=x){ + int count=0; + for(Node n:w.succ){ + + Edge e=G.getEdge(w,n); + count++; + if(count!=1){ + //Embed recursivly + //tree edge + if(((PlanarityNodePayload)w.data).dfsNum < ((PlanarityNodePayload)e.destination.data).dfsNum){ + int tprime=(t==((PlanarityEdgePayload)e.data).alpha) ? LEFT : RIGHT; + embedding(e,G, H,tprime,Tprime,Aprime); + } + else{ + Tprime.add(H.getEdge(H.getNode(e.source.ID),H.getNode(e.destination.ID))); + Aprime.add(H.getEdge(H.getNode(e.destination.ID),H.getNode(e.source.ID))); + } + //update lists T, Al and Ar + if(t==((PlanarityEdgePayload)e.data).alpha){ + Tprime.addAll(T); + T.clear(); + T.addAll(Tprime); //T= Tprime conc T + Tprime.clear(); + Al.addAll(Aprime); //Al= Al conc Aprime + Aprime.clear(); + } + else{ + T.addAll(Tprime); //T= T conc Tprime + Tprime.clear(); + Aprime.addAll(Ar); + Ar.clear(); + Ar.addAll(Aprime); //Ar= Aprime conc Ar + Aprime.clear(); + } + } + } //for + //Compute w's adjacency list and prepare for next iteration + Edge ne=((PlanarityNodePayload)w.data).treeEdgeInto; + T.add(H.getEdge(H.getNode(ne.destination.ID),H.getNode(ne.source.ID))); + + for(Edge e: T) ((EmbeddingEdgePayload)e.data).sortNum=cur_nr++; + T.clear(); + while(!Al.isEmpty() && Al.getLast().source ==H.getNode(((PlanarityNodePayload)w.data).parent.ID)){ + T.addFirst(Al.removeLast()); + } + ne=((PlanarityNodePayload)w.data).treeEdgeInto; + T.add(H.getEdge(H.getNode(ne.source.ID),H.getNode(ne.destination.ID))); + while(!Ar.isEmpty() && Ar.getFirst().source == H.getNode(((PlanarityNodePayload)w.data).parent.ID)){ + T.add(Ar.removeFirst()); + } + + w=((PlanarityNodePayload)w.data).parent; + }//while + + //Prepare the output + A.clear(); + A.addAll(Ar); + Ar.clear(); + A.add(H.getEdge(H.getNode(back_edge_into_w0.destination.ID),H.getNode(back_edge_into_w0.source.ID))); + A.addAll(Al); + Al.clear(); + } + + /** + * Reverses the edge e + */ + public boolean reverseEdge(Edge e){ + if(edges.contains(e)){ + Node source=e.source; + Node dest=e.destination; + source.succ.remove(dest); + dest.pred.remove(source); + source.pred.add(dest); + dest.succ.add(source); + e.reverseEdge(); + return true; + } + return false; + } + + + /** + * This class encapsulates the payload a node + * has to carry turing the planarization step. + */ + public class PlanarityNodePayload{ + public int dfsNum=0; + public Node parent=null; + public boolean reached=false; + public int lowpt1=0; + public int lowpt2=0; + //Embedding vars + public Edge treeEdgeInto=null; + } + + /** + * This class encapsulates the payload a edge + * has to carry turing the planarization step. + */ + public class PlanarityEdgePayload{ + public int alpha=0; + public int cost=0; + } + + /** + * This class encapsulates the payload a edge + * has to carry turing the planarisation step. + */ + public class EmbeddingEdgePayload{ + public int sortNum=0; + } + + + /** + * This class encapsulates the payloade a node + * has to carry within the algotithm for making it + * biconnectional + */ + public class BiConPayload{ + public int dfsNum=0; + public int lowPt=0; + public boolean reached=false; + public Node parent=null; + } + +} diff --git a/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Edge.java b/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Edge.java new file mode 100644 index 000000000000..b530240ce125 --- /dev/null +++ b/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Edge.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.graphhelper; + +/** + * Implements the edge of an directed graph. + * + * @author Stefan Loidl + */ +public class Edge { + + public Node source, destination; + + /** data field for the use with user algorithms*/ + public Object data=null; + + public boolean visited=false; + + /** + * Creates a new edge form source to destination Node + */ + public Edge(Node source, Node destination) { + this.source=source; + this.destination=destination; + } + + /** + * Returns a new Edge from destination to source. + */ + public Edge getReverseEdge(){ + return new Edge(destination,source); + } + + /** + * Reverses this Edge. + */ + public void reverseEdge(){ + Node n=source; + source=destination; + destination=n; + } +} diff --git a/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Embedding.java b/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Embedding.java new file mode 100644 index 000000000000..3a68ffe083d0 --- /dev/null +++ b/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Embedding.java @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.graphhelper; + +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; + +/** + * This class is a helper that creates an embedding of a DiGraph + * by traversing the nodes (and their edges) according to the clockwise + * and counter clockwise ordering of the edges within the node. + * Before creating an instance the DiGraph must be: + * - planarized + * - embedded + * The embedding is represented by a finite list of faces. + * + * @author Stefan Loidl + */ +public class Embedding { + + private DiGraph graph; + private LinkedList faces=new LinkedList(); + private Face infinityFace=null; + + + /** + * Creates an embedding from a planar DiGraph with + * Edges sorted clockwise. Bidirected Edges are not allowed and + * therefore broken within this class. + */ + public Embedding(DiGraph dg) { + graph=dg; + graph.breakBiDirection(); + + Collection edges=dg.getEdges(); + for(Edge e:edges) e.data=new EdgePayload(); + for(Edge e:edges){ + EdgePayload ep=(EdgePayload)e.data; + if(ep.left==null){ + Face p=new Face(); + createFace(e,e,p,true, true); + for(Edge ed:p.edges){ + EdgePayload payload=(EdgePayload)ed.data; + if(payload.left==null) payload.left=p; + else payload.right=p; + } + faces.add(p); + } + if(ep.right==null){ + Face p=new Face(); + createFace(e,e,p,true, false); + if(ep.left.equals(p)){ + p=new Face(); + createFace(e,e,p,true, true); + } + for(Edge ed:p.edges){ + EdgePayload payload=(EdgePayload)ed.data; + if(payload.left==null) payload.left=p; + else payload.right=p; + } + faces.add(p); + } + } + + //Determine the infinity face as the plane + //with the most surrounding edges + int max=0; + for(Face p:faces){ + if(p.edges.size()>max){ + max=p.edges.size(); + infinityFace=p; + } + } + } + + /** + * Creates a new face by traversing the graph according to the + * clockwise ordering of the edges. + */ + private void createFace(Edge first, Edge e, Face p, boolean forward, boolean left){ + Edge next; + int index; + p.edges.add(e); + //calculate next Edge + Node n; + if(forward) n=e.destination; + else n=e.source; + + if(left){ + index=n.edges.indexOf(e); + index++; + if(index >= n.edges.size()) index=0; + next=n.edges.get(index); + } + else{ + index=n.edges.indexOf(e); + index--; + if(index < 0) index=n.edges.size()-1; + next=n.edges.get(index); + } + + forward=(next.source==n); + if(next==first) { + Node no; + if(forward) no=next.destination; + else no=next.source; + if(no.edges.size()==1) p.edges.add(next); + return; + } + + createFace(first,next,p,forward,left); + } + + /** + * Returns the graph. + */ + public DiGraph getGraph(){ + return graph; + } + + /** + * Returns the faces of the embedding. + */ + public LinkedList getFaces(){ + return faces; + } + + /** + * Returns the infinity face. + */ + public Face getInfinityFace(){ + return infinityFace; + } + + /** + * Class representing a face + */ + public class Face{ + + //surrounding edges + public LinkedList edges=new LinkedList(); + + /** + * Overriden equals + */ + public boolean equals(Object o){ + if(!(o instanceof Face)) return false; + Face p=(Face)o; + + if(edges.size()!=p.edges.size()) return false; + + for(Edge e:p.edges) if(!edges.contains(e)) return false; + return true; + } + + /** + * Returns the nodes within the face. + */ + public Collection getNodes(){ + LinkedList n=new LinkedList(); + for(Edge e:edges){ + n.add(e.source); + n.add(e.destination); + } + return n; + } + + /** + * For debug reasons mainly + */ + public String toString(){ + StringBuffer buf=new StringBuffer(); + for(Edge e: edges){ + buf.append("("+e.source.ID+","+e.destination.ID+"), "); + } + return buf.substring(0,buf.length()-2); + } + } + + + + /** + * Adds left and right face to edge via payload data. + */ + public class EdgePayload{ + public Face left=null; + public Face right=null; + } + + + /** + * For debug reasons mainly + */ + public String toString(){ + StringBuffer buf=new StringBuffer(); + buf.append("\n"+graph.toString()+"\n"); + + int i=0; + for(Face p:faces){ + buf.append(i+": "+p.toString()+"\n"); + i++; + } + buf.append("\n"); + for(Node n:graph.getNodes()){ + buf.append(n.ID+" "); + for(Edge e:n.edges){ + buf.append("("+e.source.ID+","+e.destination.ID+") "); + } + buf.append("\n"); + } + return buf.toString(); + } + + /** + * Adds an edge, splitting the face between the two nodes. + */ + void addFaceEdge(Face face, Node from, Node to) { + if(face==null || from==null || to==null) return; + assert faces.contains(face); + assert face.getNodes().contains(from); + assert face.getNodes().contains(to); + + Edge lFrom=null, rFrom=null; + //find the two edges containing from + for(Edge e:face.edges){ + if(e.source==from || e.destination==from){ + if(lFrom==null) lFrom=e; + else{ + rFrom=e; + break; + } + } + } + + //make shure lFrom has smaller index + if(face.edges.indexOf(lFrom) > face.edges.indexOf(rFrom)){ + Edge t=rFrom; + rFrom=lFrom; + lFrom=t; + } + + //find the right half of the face from "from" to "to" + LinkedList rightPart=new LinkedList(); + int index=face.edges.indexOf(rFrom); + while(true){ + Edge e=face.edges.get(index); + rightPart.add(e); + if(e.destination==to || e.source==to) break; + index=(index+1)%face.edges.size(); + } + + //find the right half of the face from "from" to "to" + int prevIndex=index; + LinkedList leftPart=new LinkedList(); + index=face.edges.indexOf(lFrom); + while(index!=prevIndex){ + Edge e=face.edges.get(index); + leftPart.add(e); + index--; + if(index<0) index=face.edges.size()-1; + } + + //Attention! Clockwise ordering is destroyed here + Edge newedge=new Edge(from,to); + graph.addEdge(newedge); + + leftPart.add(newedge); + rightPart.add(newedge); + + //Split faces. + face.edges=leftPart; + Face newface=new Face(); + newface.edges=rightPart; + faces.add(newface); + } +} diff --git a/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Node.java b/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Node.java new file mode 100644 index 000000000000..6e018ca4ccc7 --- /dev/null +++ b/visualizer/C1Visualizer/GraphHelper/src/main/java/at/ssw/visualizer/graphhelper/Node.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.graphhelper; + +import java.util.LinkedList; + +/** + * Implements a node of the directed graph. + * + * @author Stefan Loidl + */ +public class Node { + + /**unique identifier*/ + public String ID; + + /** flag used within algorithms*/ + public boolean visited; + + /** all edges adjacent to the node*/ + public LinkedList edges; + + /** + * List of successors and predecessors of the node + * these lists are not meant to be changed by the user. + * they are modified when inserting edges + */ + public LinkedList succ,pred; + + /** data field for the use with algorithms*/ + public Object data=null; + + /** Creates a new instance of Node- ID should be unique within the graph*/ + public Node(String ID) { + this.ID=ID; + visited=false; + succ=new LinkedList(); + pred=new LinkedList(); + edges=new LinkedList(); + } + + +} diff --git a/visualizer/C1Visualizer/GraphHelper/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/GraphHelper/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..55ebdc16403d --- /dev/null +++ b/visualizer/C1Visualizer/GraphHelper/src/main/nbm/manifest.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.graphhelper +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/graphhelper/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/GraphHelper/src/main/resources/at/ssw/visualizer/graphhelper/Bundle.properties b/visualizer/C1Visualizer/GraphHelper/src/main/resources/at/ssw/visualizer/graphhelper/Bundle.properties new file mode 100644 index 000000000000..4185a17976b2 --- /dev/null +++ b/visualizer/C1Visualizer/GraphHelper/src/main/resources/at/ssw/visualizer/graphhelper/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Graph Helper diff --git a/visualizer/C1Visualizer/GraphLayoutAPI/pom.xml b/visualizer/C1Visualizer/GraphLayoutAPI/pom.xml new file mode 100644 index 000000000000..3bf2c013f7a3 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutAPI/pom.xml @@ -0,0 +1,58 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + GraphLayoutAPI + 1.13-SNAPSHOT + nbm + GraphLayoutAPI + + UTF-8 + + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.positionmanager + at.ssw.positionmanager.export + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Cluster.java b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Cluster.java new file mode 100644 index 000000000000..2766a2266c43 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Cluster.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.positionmanager; + +import java.util.Set; + +/** + * + * @author Thomas Wuerthinger + */ +public interface Cluster { + public Cluster getOuter(); + public Set getSuccessors(); + public Set getPredecessors(); +} diff --git a/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/LayoutGraph.java b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/LayoutGraph.java new file mode 100644 index 000000000000..5b117f101141 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/LayoutGraph.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.positionmanager; + +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * + * @author Thomas Wuerthinger + */ +public class LayoutGraph { + + + private Set links; + private SortedSet vertices; + private Hashtable> inputPorts; + private Hashtable> outputPorts; + private Hashtable> portLinks; + + + public LayoutGraph(Set links) { + this(links, new HashSet()); + } + + /** Creates a new instance of LayoutGraph */ + public LayoutGraph(Set links, Set additionalVertices) { + this.links = links; + assert verify(); + + vertices = new TreeSet(); + portLinks = new Hashtable>(); + inputPorts = new Hashtable>(); + outputPorts = new Hashtable>(); + + for(Link l : links) { + Port p = l.getFrom(); + Port p2 = l.getTo(); + Vertex v1 = p.getVertex(); + Vertex v2 = p2.getVertex(); + + if(!vertices.contains(v1)) { + + outputPorts.put(v1, new HashSet()); + inputPorts.put(v1, new HashSet()); + vertices.add(v1); + } + + if(!vertices.contains(v2)) { + vertices.add(v2); + outputPorts.put(v2, new HashSet()); + inputPorts.put(v2, new HashSet()); + } + + if(!portLinks.containsKey(p)) { + HashSet hashSet = new HashSet(); + portLinks.put(p, hashSet); + } + + if(!portLinks.containsKey(p2)) { + portLinks.put(p2, new HashSet()); + } + + outputPorts.get(v1).add(p); + inputPorts.get(v2).add(p2); + + portLinks.get(p).add(l); + portLinks.get(p2).add(l); + } + + for(Vertex v : additionalVertices) { + if(!vertices.contains(v)) { + outputPorts.put(v, new HashSet()); + inputPorts.put(v, new HashSet()); + vertices.add(v); + } + } + } + + public Set getInputPorts(Vertex v) { + return this.inputPorts.get(v); + } + + public Set getOutputPorts(Vertex v) { + return this.outputPorts.get(v); + } + + public Set getPortLinks(Port p) { + return portLinks.get(p); + } + + public Set getLinks() { + return links; + } + + public boolean verify() { + return true; + } + + public SortedSet getVertices() { + return vertices; + } + + private void markNotRoot(Set notRootSet, Set visited, Vertex v, Vertex startingVertex) { + + if(visited.contains(v)) return; + if(v != startingVertex) { + notRootSet.add(v); + } + visited.add(v); + Set outPorts = getOutputPorts(v); + for(Port p : outPorts) { + Set links = getPortLinks(p); + for(Link l : links) { + Port other = l.getTo(); + Vertex otherVertex = other.getVertex(); + markNotRoot(notRootSet, visited, otherVertex, startingVertex); + } + } + } + + // Returns a set of vertices with the following properties: + // - All Vertices in the set startingRoots are elements of the set. + // - When starting a DFS at every vertex in the set, every vertex of the + // whole graph is visited. + public Set findRootVertices(Set startingRoots) { + + Set notRootSet = new HashSet(); + for(Vertex v : startingRoots) { + if(!notRootSet.contains(v)) { + Set visited = new HashSet(); + markNotRoot(notRootSet, visited, v, v); + } + } + + Set vertices = getVertices(); + for(Vertex v : vertices) { + if(!notRootSet.contains(v)) { + Set visited = new HashSet(); + markNotRoot(notRootSet, visited, v, v); + } + } + + Set result = new HashSet(); + for(Vertex v : vertices) { + if(!notRootSet.contains(v)) { + result.add(v); + } + } + + return result; + } + + + public Set findRootVertices() { + return findRootVertices(new HashSet()); + } + + public Set getClusters() { + + Set clusters = new HashSet(); + for(Vertex v : getVertices()) { + if(v.getCluster() != null) { + clusters.add(v.getCluster()); + } + } + + return clusters; + } + +} diff --git a/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/LayoutManager.java b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/LayoutManager.java new file mode 100644 index 000000000000..f4050a2acfe0 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/LayoutManager.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.positionmanager; + +/** + * + * @author Thomas Wuerthinger + */ +public interface LayoutManager { + + public void doLayout(LayoutGraph graph); + public void doRouting(LayoutGraph graph); + +} diff --git a/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Link.java b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Link.java new file mode 100644 index 000000000000..288df1010087 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Link.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.positionmanager; + +import java.awt.Point; +import java.util.List; + +/** + * + * @author Thomas Wuerthinger + */ +public interface Link { + public Port getFrom(); + public Port getTo(); + public List getControlPoints(); + public void setControlPoints(List list); +} diff --git a/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Port.java b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Port.java new file mode 100644 index 000000000000..aa5b93167e80 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Port.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.positionmanager; + +import java.awt.Point; + +/** + * + * @author Thomas Wuerthinger + */ +public interface Port { + + public Vertex getVertex(); + public Point getRelativePosition(); + +} diff --git a/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Vertex.java b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Vertex.java new file mode 100644 index 000000000000..e28b284fa325 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/Vertex.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.positionmanager; + +import java.awt.Dimension; +import java.awt.Point; + +/** + * + * @author Thomas Wuerthinger + */ +public interface Vertex extends Comparable{ + + public Cluster getCluster(); + public Dimension getSize(); + public Point getPosition(); + public void setPosition(Point p); + public boolean isDirty(); + public boolean isRoot(); + + public boolean isExpanded(); + public boolean isFixed(); + public boolean isMarked(); +} diff --git a/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/export/GMLFileExport.java b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/export/GMLFileExport.java new file mode 100644 index 000000000000..f89a747466a7 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/export/GMLFileExport.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.positionmanager.export; + +import at.ssw.positionmanager.LayoutGraph; +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Vertex; +import java.awt.Color; +import java.awt.Point; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Hashtable; +import java.util.Map; + +/** + * + * @author Stefan Loidl + */ +public class GMLFileExport implements LayoutGraphExporter{ + + private File exportFile; + private Map names; + Map colors; + + /** Creates a new instance of GMLFileExport */ + public GMLFileExport(File f) { + exportFile=f; + } + + public GMLFileExport(File f, Map nameList, Map colors){ + exportFile=f; + names=nameList; + this.colors=colors; + } + + public boolean export(LayoutGraph lg) { + if(lg==null || exportFile==null) return false; + Hashtable index=new Hashtable(); + try{ + FileWriter fw=new FileWriter(exportFile); + fw.write("Creator \"Graph Visualizer SSW\"\nVersion 1.0"); + fw.write("\ngraph [\n\tdirected 1\n"); + + int i=0; + //export nodes + for(Vertex v: lg.getVertices()){ + index.put(v,new Integer(i)); + exportVertex(v,i,fw); + i++; + } + //export edges + for(Link l: lg.getLinks()){ + int from=index.get(l.getFrom().getVertex()).intValue(); + int to=index.get(l.getTo().getVertex()).intValue(); + exportLink(l,from,to,fw); + } + + fw.write("]"); + fw.close(); + } catch(Exception e){ + return false; + } + return true; + } + + + + private void exportVertex(Vertex v, int id, FileWriter fw) throws IOException { + StringBuffer buf=new StringBuffer(); + buf.append("\tnode [\n\t\tid "); + buf.append(id); + buf.append("\n\t\tlabel \""); + if(names!=null){ + buf.append(names.get(v)); + }else buf.append(id); + buf.append("\"\n\t\tlabelAnchor \"c\"\n\t\tgraphics [\n\t\t\tx "); + buf.append((double)v.getPosition().x); + buf.append("\n\t\t\ty "); + buf.append((double)v.getPosition().y); + buf.append("\n\t\t\tw "); + buf.append((double)v.getSize().width); + buf.append("\n\t\t\th "); + buf.append((double)v.getSize().height); + buf.append("\n\t\t\ttype \"rectangle\"\n\t\t\tfill \"#"); + if(colors!=null){ + Color c=colors.get(v); + if(c==null) c=Color.WHITE; + buf.append(Integer.toHexString(c.getRed())); + buf.append(Integer.toHexString(c.getGreen())); + buf.append(Integer.toHexString(c.getBlue())); + } + else buf.append("FFFFFF"); + buf.append("\""); + buf.append("\n\t\t\toutline \"#000000\"\n\t\t]"); + buf.append("\n\t\tLabelGraphics [\n\t\t\ttype \"text\"\n\t\t\tfill \"#000000\""); + buf.append("\n\t\t\tanchor \"c\"\n\t\t]\n\t]\n"); + fw.write(buf.toString()); + } + + private void exportLink(Link l, int from, int to, FileWriter fw) throws IOException { + StringBuffer buf=new StringBuffer(); + buf.append("\tedge [\n\t\tsource "); + buf.append(from); + buf.append("\n\t\ttarget "); + buf.append(to); + buf.append("\n\t\tgraphics [\n\t\t\ttype \"line\""); + buf.append("\n\t\t\tarrow \"last\""); + if(l.getControlPoints()!=null && l.getControlPoints().size()>0){ + buf.append("\n\t\t\tLine ["); + for(Point p:l.getControlPoints()){ + buf.append("\n\t\t\t\tpoint [ x "); + buf.append((double)p.x); + buf.append(" y "); + buf.append((double)p.y); + buf.append(" ]"); + } + buf.append("\n\t\t\t]"); + } + buf.append("\n\t\t]\n\t]\n"); + fw.write(buf.toString()); + } + +} diff --git a/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/export/LayoutGraphExporter.java b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/export/LayoutGraphExporter.java new file mode 100644 index 000000000000..bb81ae7f2552 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/java/at/ssw/positionmanager/export/LayoutGraphExporter.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.positionmanager.export; + +import at.ssw.positionmanager.LayoutGraph; + +/** + * Instances of this interface can be used to export the LayoutGraph + * to different formats. + * + * @author Stefan Loidl + */ +public interface LayoutGraphExporter { + /** Returns if the export was successful*/ + public boolean export(LayoutGraph lg); +} diff --git a/visualizer/C1Visualizer/GraphLayoutAPI/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..ec3b480bc596 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/nbm/manifest.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.positionmanager +OpenIDE-Module-Localizing-Bundle: at/ssw/positionmanager/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/GraphLayoutAPI/src/main/resources/at/ssw/positionmanager/Bundle.properties b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/resources/at/ssw/positionmanager/Bundle.properties new file mode 100644 index 000000000000..a6faff40caa4 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutAPI/src/main/resources/at/ssw/positionmanager/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Graph Layout API diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/pom.xml b/visualizer/C1Visualizer/GraphLayoutImpl/pom.xml new file mode 100644 index 000000000000..dc90eccc75f1 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/pom.xml @@ -0,0 +1,76 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + GraphLayoutImpl + 1.13-SNAPSHOT + nbm + GraphLayoutImpl + + UTF-8 + + + + at.ssw.visualizer + GraphLayoutAPI + + ${project.version} + + + at.ssw.visualizer + GraphHelper + + ${project.version} + + + org.eclipse + draw2d + 3.2.100-v20070529 + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.dataflow.layout + at.ssw.dataflow.options + at.ssw.graphanalyzer.positioning + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/CompoundForceLayouter.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/CompoundForceLayouter.java new file mode 100644 index 000000000000..29d1f227b423 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/CompoundForceLayouter.java @@ -0,0 +1,511 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.layout; + +import at.ssw.dataflow.options.BooleanStringValidator; +import at.ssw.dataflow.options.DoubleStringValidator; +import at.ssw.dataflow.options.IntStringValidator; +import at.ssw.dataflow.options.Validator; +import at.ssw.positionmanager.Cluster; +import at.ssw.positionmanager.LayoutGraph; +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Vertex; +import at.ssw.visualizer.graphhelper.DiGraph; +import at.ssw.visualizer.graphhelper.Edge; +import at.ssw.visualizer.graphhelper.Node; +import java.awt.Point; +import java.awt.geom.Point2D; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Random; + + +/** + * This class implements a force directed layout algorithm. It uses the well + * known spring model that relys on springs and electrical forces. To implement + * clustering additional virtual nodes are inserted to the original graph. + * For each cluster one such node is inserted that is connected to all of the + * nodes within the cluster. Moreover it is connected to each cluster that has + * links common with the current cluster. + * + * @author Stefan Loidl + */ +public class CompoundForceLayouter implements ExternalGraphLayouter{ + + //Parameters for the force model + private double SPRINGLEN = 50; + private double STIFFNESS = 15; + private double REPULSION = 100; + + //forces between expanded nodes in the same cluster + private double SPRINGLENEXP = 50; + private double STIFFNESSEXP = 15; + private double REPULSIONEXP = 300; + + //forces between nodes within different clusters + private double OVERCLUSTERSPRINGLEN = 70; + private double OVERCLUSTERSTIFFNESS = 15; + private double OVERCLUSTERREPULSION = 200; + + //Forces between real and virtual nodes + private double CLUSTERSPRINGLEN = 50; + private double CLUSTERSTIFFNESS = 15; + private double CLUSTERREPULSION = 100; + + //Forces between virtual nodes + private double INTERCLUSTERSPRINGLEN = 300; + private double INTERCLUSTERSTIFFNESS = 30; + private double INTERCLUSTERREPULSION = 800; + + //During one cycle one node may only move this distance + private double MAXIMUMMOVEMENT=500; + + //Space between the connected components + private int PADDING=30; + + //Iterations the algorithms uses. + private int ITERATIONS=100; + + //Seed for the random node positioning + private long SEED=133; + + //Use logarithmic spring model. The alternative is hookes law. + private boolean USELOGSPRINGS=false; + + //Reuse current node positions for further optimizations + //the alternative is to use random positions. + private boolean USECURRENTNODEPOSITIONS=false; + + //Current positions of the nodes + private Hashtable posList; + + + //Performs the layout task. + public void doLayout(LayoutGraph graph) { + Hashtable idtoverticles=new Hashtable(); + Hashtable verticlestoid=new Hashtable(); + + DiGraph dg=new DiGraph(); + Iterator iter=graph.getVertices().iterator(); + + //Add Nodes to Graphhelper + int i=0; + while(iter.hasNext()){ + String id=String.valueOf(i); + dg.addNode(new Node(id)); + Vertex v=iter.next(); + + idtoverticles.put(id,v); + verticlestoid.put(v,id); + i++; + } + + //Add Edges to Graphhelper + for(Link l: graph.getLinks()){ + String from=verticlestoid.get(l.getFrom().getVertex()); + String to=verticlestoid.get(l.getTo().getVertex()); + + dg.addEdge(new Edge(dg.getNode(from),dg.getNode(to))); + } + + //Add virtual cluster Nodes to Graphhelper + iter=graph.getVertices().iterator(); + Hashtable virtualNodes=new Hashtable(); + Node defaultNode=new Node(String.valueOf(i++)); + dg.addNode(defaultNode); + boolean defNodeUsed=false; + + while(iter.hasNext()){ + Vertex v=iter.next(); + Cluster c=v.getCluster(); + Node vn; + + if(c==null) { + vn=defaultNode; + defNodeUsed=true; + } + else{ + if(virtualNodes.containsKey(c)) vn=virtualNodes.get(c); + else{ + String id=String.valueOf(i++); + vn=new Node(id); + virtualNodes.put(c,vn); + dg.addNode(vn); + } + } + + dg.addEdge(new Edge(vn,dg.getNode(verticlestoid.get(v)))); + } + + //Create structure to identify virtual nodes later-on + HashSet vNodes=new HashSet(); + for(Node n: virtualNodes.values()) vNodes.add(n.ID); + + //nodes with no cluster are added to defaultnode. If none exists + //then delete the node. + if(!defNodeUsed) dg.removeNode(defaultNode); + else vNodes.add(defaultNode.ID); + + //Add cluster links to Graphhelper + for(Cluster c: virtualNodes.keySet()){ + if(c.getSuccessors()!=null){ + for(Cluster c1: c.getSuccessors()){ + dg.addEdge(new Edge(virtualNodes.get(c),virtualNodes.get(c1))); + } + } + } + + //Perform layout + layout(graph,dg,idtoverticles,verticlestoid, vNodes); + } + + + /* Performs the layout of the spring algorithm via an iterative relaxation approach*/ + private void layout(LayoutGraph lg, DiGraph digraph, Hashtable idtoverticles, Hashtable verticlestoid, HashSet vNodes) { + int lastmax=0; + int max=0; + for(DiGraph dg:digraph.getConnectedComponents()){ + + //initialize positions with random numbers + posList=new Hashtable(); + Random rand=new Random(SEED); + for(Node w: dg.getNodes()){ + if(USECURRENTNODEPOSITIONS){ + Vertex v=idtoverticles.get(w.ID); + double x=0.0,y=0.0; + if(v!=null){ + x=v.getPosition().x; + y=v.getPosition().y; + } + posList.put(w.ID,new doublePoint(x,y)); + } + else posList.put(w.ID,new doublePoint(rand.nextDouble()*300d,rand.nextDouble()*300d)); + } + + //perform several relaxation iterations + for(int i=0; ip.getX()) smallestX=p.getX(); + if(smallestY>p.getY()) smallestY=p.getY(); + } + + max=0; + for(Node w: dg.getNodes()){ + Vertex v=idtoverticles.get(w.ID); + if(v==null) continue; + + Point2D p=posList.get(w.ID); + int x=((int)(p.getX()-smallestX))+lastmax+PADDING; + int y=((int)(p.getY()-smallestY))+PADDING; + + if(x+v.getSize().width >max) max=x+v.getSize().width; + v.setPosition(new Point(x, y)); + } + lastmax=max; + } + } + + + /* + * Calculates the forces on node n and moves it a small distance to lessen + * it. Forces for virtual nodes are different from others but do the same + * job. + */ + private void relaxation(Node n, DiGraph dg, HashSet vNodes, Hashtable idtoverticles){ + + double X = posList.get(n.ID).getX(); + double Y = posList.get(n.ID).getY(); + + LinkedList adjacentVertices=new LinkedList(); + + Vertex v=idtoverticles.get(n.ID); + + // Get all adjacent Nodes + for(Node pre: n.pred){ + if(!n.ID.equals(pre.ID)) + adjacentVertices.add(pre); + } + for(Node succ: n.succ){ + if(!n.ID.equals(succ.ID)) + adjacentVertices.add(succ); + } + + //Calcualte Spring len between all adjacent Nodes + double SpringX = 0, SpringY = 0; + for(Node adjacent:adjacentVertices) { + double stiffness, springlen; + + if(vNodes.contains(n.ID) || vNodes.contains(adjacent.ID)){ + if(vNodes.contains(n.ID) && vNodes.contains(adjacent.ID)){ + stiffness=INTERCLUSTERSTIFFNESS; + springlen=INTERCLUSTERSPRINGLEN; + } + else{ + stiffness=CLUSTERSTIFFNESS; + springlen=CLUSTERSPRINGLEN; + } + } + else{ + Vertex v1=idtoverticles.get(adjacent.ID); + if((v1!=null && v!=null)&& v1.getCluster()!=v.getCluster()){ + stiffness=OVERCLUSTERSTIFFNESS; + springlen=OVERCLUSTERSPRINGLEN; + } + else{ + if((v1!=null && v!=null)&&(v1.isExpanded()||v.isExpanded())){ + stiffness=STIFFNESSEXP; + springlen=SPRINGLENEXP; + }else{ + stiffness=STIFFNESS; + springlen=SPRINGLEN; + } + } + } + + double adjX = posList.get(adjacent.ID).getX(); + double adjY = posList.get(adjacent.ID).getY(); + + double distance = Point2D.distance( adjX, adjY, X, Y ); + //Minimum distance between nodes! + if(distance == 0) distance = 0.01d; + + if(USELOGSPRINGS){ + //Logarithmic Springs + SpringX +=stiffness*Math.log(distance/springlen)*((X-adjX)/distance); + SpringY +=stiffness*Math.log(distance/springlen)*((Y-adjY)/distance); + } + else{ + //Hookes Law with relativ springkonstant + SpringX +=stiffness*((distance-springlen)/(2*springlen)) *((X-adjX)/distance); + SpringY +=stiffness*((distance-springlen)/(2*springlen)) *((Y-adjY)/distance); + } + } + + //Calcualte Repulsion + double RepulsionX = 0, RepulsionY = 0; + for(Node w: dg.getNodes()) { + if(w == n) continue; + + double repulsion; + if(vNodes.contains(n.ID) || vNodes.contains(w.ID)) { + if(vNodes.contains(n.ID) && vNodes.contains(w.ID)){ + repulsion=INTERCLUSTERREPULSION; + } + else{ + repulsion=CLUSTERREPULSION; + } + } + else { + Vertex v1=idtoverticles.get(w.ID); + if((v1!=null && v!=null)&& v1.getCluster()!=v.getCluster()){ + repulsion=OVERCLUSTERREPULSION; + } + else { + if((v1!=null && v!=null)&&(v1.isExpanded()||v.isExpanded())) + repulsion=REPULSIONEXP; + else repulsion=REPULSION; + } + } + + double nX = posList.get(w.ID).getX(); + double nY = posList.get(w.ID).getY(); + + double distance = Point2D.distance( nX, nY, X, Y ); + if(distance == 0) distance = 0.01d; + + //If Spring energy is positiv- this one is negativ + RepulsionX -= (repulsion/distance)*((X-nX)/distance); + RepulsionY -= (repulsion/distance)*((Y-nY)/distance); + } + + // Move Node in direction of the force + double dx = -(SpringX + RepulsionX); + double dy = -(SpringY + RepulsionY); + //Make shure the node moves not too far off the others + if(dx>MAXIMUMMOVEMENT) dx=MAXIMUMMOVEMENT; + if(dx*-1>MAXIMUMMOVEMENT) dx=-MAXIMUMMOVEMENT; + if(dy>MAXIMUMMOVEMENT) dy=MAXIMUMMOVEMENT; + if(dy*-1>MAXIMUMMOVEMENT) dy=-MAXIMUMMOVEMENT; + + Point2D p=posList.get(n.ID); + p.setLocation(p.getX()+dx,p.getY()+dy); + } + + /* Performs the routing via a simple direct line router */ + public void doRouting(LayoutGraph graph) { + RoutingHelper.doRouting(graph); + } + + public boolean isClusteringSupported() { + return true; + } + + public boolean isAnimationSupported() { + return true; + } + + public boolean isMovementSupported() { + return true; + } + + public void setUseCurrentNodePositions(boolean b) { + USECURRENTNODEPOSITIONS=b; + } + + // + private static String[] options={"Componentpadding", "Springlength","Over Cl. Sp.Length", "Virtual Springlength", "Stiffness","Over Cl. Stiffness", "Virtual Stiffness", "Iterations", "Repulsion","Over Cl. Repulsion","Virtual Repulsion", "Seed", "Log. Springs"}; + private static String[] descriptions={"Minimum space between connected components", + "Length of the spring between nodes in the same cluster","Length of the spring between nodes in different clusters","Length of the spring between virtual nodes", + "Stiffness of the spring between the nodes in the same cluster","Stiffness of the spring between the nodes in different clusters", "Stiffness of the spring between the virtual nodes", + "Relaxation iterations the algorithm performs", + "Repulsion between nodes in the same cluster","Repulsion between nodes in different clusters", "Repulsion between vitual nodes", + "Seed for the random prepositioning", "Logarithmic spring simulation is used?"}; + private static Class[] optionclass={String.class,String.class, String.class,String.class,String.class,String.class,String.class}; + private static Validator[] validators={ + new IntStringValidator(0,1000), //padding + new DoubleStringValidator(0.0d,1000.0d), //Springlen + new DoubleStringValidator(0.0d,1000.0d), //Over cluster Springlen + new DoubleStringValidator(0.0d,1000.0d), //Virtual Springlen + new DoubleStringValidator(0.0d,1000.0d), //Stiffness + new DoubleStringValidator(0.0d,1000.0d), //over cluster Stiffness + new DoubleStringValidator(0.0d,1000.0d), //virtual Stiffness + new IntStringValidator(0,5000), //Iterations + new DoubleStringValidator(0.0d,10000.0d), //Repulsion + new DoubleStringValidator(0.0d,10000.0d), //Over cluster Repulsion + new DoubleStringValidator(0.0d,10000.0d), //Virtual Repulsion + new IntStringValidator(0,Integer.MAX_VALUE), //Seed + new BooleanStringValidator() //Log Spring + }; + + + public String[] getOptionKeys() { + return options; + } + + public boolean setOption(String key, Object value) { + if(key==null) return false; + + int i=getIndexForKey(key); + if(i==-1) return false; + + if(validators[i].validate(value)){ + switch(i){ + case 0: PADDING=Integer.parseInt((String)value); break; + case 1: SPRINGLEN=Double.parseDouble((String)value);break; + case 2: OVERCLUSTERSPRINGLEN=Double.parseDouble((String)value);break; + case 3: INTERCLUSTERSPRINGLEN=Double.parseDouble((String)value);break; + case 4: STIFFNESS=Double.parseDouble((String)value);break; + case 5: OVERCLUSTERSTIFFNESS=Double.parseDouble((String)value);break; + case 6: INTERCLUSTERSTIFFNESS=Double.parseDouble((String)value);break; + case 7: ITERATIONS=Integer.parseInt((String)value);break; + case 8: REPULSION=Double.parseDouble((String)value);break; + case 9: OVERCLUSTERREPULSION=Double.parseDouble((String)value);break; + case 10: INTERCLUSTERREPULSION=Double.parseDouble((String)value);break; + case 11: SEED=Integer.parseInt((String)value);break; + case 12: USELOGSPRINGS=Boolean.parseBoolean((String)value);break; + default: return false; + } + return true; + } + return false; + } + + + public Object getOption(String key) { + if(key==null) return null; + + int i=getIndexForKey(key); + + switch(i){ + case 0: return String.valueOf(PADDING); + case 1: return String.valueOf(SPRINGLEN); + case 2: return String.valueOf(OVERCLUSTERSPRINGLEN); + case 3: return String.valueOf(INTERCLUSTERSPRINGLEN); + case 4: return String.valueOf(STIFFNESS); + case 5: return String.valueOf(OVERCLUSTERSTIFFNESS); + case 6: return String.valueOf(INTERCLUSTERSTIFFNESS); + case 7: return String.valueOf(ITERATIONS); + case 8: return String.valueOf(REPULSION); + case 9: return String.valueOf(OVERCLUSTERREPULSION); + case 10: return String.valueOf(INTERCLUSTERREPULSION); + case 11: return String.valueOf(SEED); + case 12: return String.valueOf(USELOGSPRINGS); + default: return null; + } + } + + public int getIndexForKey(String key){ + for(int i=0;i + + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/CompoundHierarchicalNodesLayouter.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/CompoundHierarchicalNodesLayouter.java new file mode 100644 index 000000000000..5ff043b7e90a --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/CompoundHierarchicalNodesLayouter.java @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.layout; + +import at.ssw.positionmanager.Cluster; +import at.ssw.positionmanager.LayoutGraph; +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Vertex; +import at.ssw.visualizer.graphhelper.DiGraph; +import at.ssw.dataflow.options.IntStringValidator; +import at.ssw.dataflow.options.Validator; +import java.awt.Point; +import java.util.Hashtable; +import java.util.Iterator; +import org.eclipse.draw2d.geometry.Insets; +import org.eclipse.draw2d.graph.CompoundDirectedGraph; +import org.eclipse.draw2d.graph.CompoundDirectedGraphLayout; +import org.eclipse.draw2d.graph.Edge; +import org.eclipse.draw2d.graph.Node; +import org.eclipse.draw2d.graph.Subgraph; + + +/** + * Positioning using Compound-Hirachical Layout from GEF draw2d library. + * This class is a wrapper that transforms the input graph models. + * + * @author Stefan Loidl + */ +public class CompoundHierarchicalNodesLayouter implements ExternalGraphLayouter{ + + //Borders of the graph within the drawing + private static final int TOP_BORDER = 20; + private static final int LEFT_BORDER = 20; + + //Padding of the nodes + private int PADDING = 50; + //Inset within the clusters + private int SUBGRAPHINSET=10; + + //This Identifier is used as name for the default cluster + private static final String DEFAULTCLUSTER="DEFAULT"; + //Top cluster holding all other clusters + private static final String TOPCLUSTER="TOP"; + + + /* Performs the layout task */ + public void doLayout(LayoutGraph graph) { + Hashtable idtoverticles=new Hashtable(); + Hashtable verticlestoid=new Hashtable(); + + DiGraph dg=new DiGraph(); + Iterator iter=graph.getVertices().iterator(); + + //Add Nodes to Graphhelper + int i=0; + while(iter.hasNext()){ + String id=String.valueOf(i); + dg.addNode(new at.ssw.visualizer.graphhelper.Node(id)); + Vertex v=iter.next(); + + idtoverticles.put(id,v); + verticlestoid.put(v,id); + i++; + } + + //Add Edges to Graphhelper + for(Link l: graph.getLinks()){ + String from=verticlestoid.get(l.getFrom().getVertex()); + String to=verticlestoid.get(l.getTo().getVertex()); + + dg.addEdge(new at.ssw.visualizer.graphhelper.Edge(dg.getNode(from),dg.getNode(to))); + } + + layout(graph,dg,idtoverticles,verticlestoid); + } + + + private void layout(LayoutGraph lg, DiGraph digraph, Hashtable idtoverticles, Hashtable verticlestoid) { + int lastmax=0; + int max=0; + for(DiGraph dg:digraph.getConnectedComponents()){ + //build draw2d compound Graph + CompoundDirectedGraph cdg=new CompoundDirectedGraph(); + + Subgraph top=new Subgraph(TOPCLUSTER); + Subgraph defaultG=new Subgraph(DEFAULTCLUSTER,top); + defaultG.innerPadding=new Insets(SUBGRAPHINSET,SUBGRAPHINSET,SUBGRAPHINSET,SUBGRAPHINSET); + cdg.nodes.add(defaultG); + cdg.nodes.add(top); + + Hashtable subgraphs=new Hashtable(); + Hashtable nodelist=new Hashtable(); + + //Add Nodes and clusters to the cdg + for(at.ssw.visualizer.graphhelper.Node n: dg.getNodes()){ + Vertex v=idtoverticles.get(n.ID); + Cluster c=v.getCluster(); + Subgraph sg; + //find or create subgraph for cluster c + if(c==null) sg=defaultG; + else{ + if(subgraphs.containsKey(c)) sg=subgraphs.get(c); + else{ + sg=new Subgraph(c.toString(),top); + sg.innerPadding=new Insets(SUBGRAPHINSET,SUBGRAPHINSET,SUBGRAPHINSET,SUBGRAPHINSET); + subgraphs.put(c,sg); + cdg.nodes.add(sg); + } + } + + Node node=new Node(v,sg); + + node.width = v.getSize().width; + node.height = v.getSize().height; + node.setPadding(new Insets(PADDING, PADDING, PADDING, PADDING)); + + cdg.nodes.add(node); + nodelist.put(n.ID,node); + } + + //Add all edges to the cdg + for(at.ssw.visualizer.graphhelper.Edge e: dg.getEdges()){ + Node n1=nodelist.get(e.source.ID); + Node n2=nodelist.get(e.destination.ID); + + //Subgraphs are chained by node relation too + if(n1.getParent()!=n2.getParent()) cdg.edges.add(new Edge(n1.getParent(),n2.getParent())); + + cdg.edges.add(new Edge(n1,n2)); + } + + + //do Layouting step + CompoundDirectedGraphLayout layout = new CompoundDirectedGraphLayout(); + layout.visit(cdg); + + //Assign positions + lastmax+=max; + max=0; + for (int i = 0; i < cdg.nodes.size(); i++) { + Node n = cdg.nodes.getNode(i); + assert n.data != null; + + //Continue with subgraphs... + if(!(n.data instanceof Vertex)) continue; + Vertex v=(Vertex)n.data; + + Point p = new Point(lastmax+ n.x + LEFT_BORDER, n.y + TOP_BORDER); + + int width=v.getSize().width; + if(n.x+width > max) max=n.x+width; + + v.setPosition(p); + } + } + } + + /* Performs the routing task via a simple direct-line router */ + public void doRouting(LayoutGraph graph) { + RoutingHelper.doRouting(graph); + } + + public boolean isClusteringSupported() { + return true; + } + + public boolean isAnimationSupported() { + return true; + } + + public boolean isMovementSupported() { + return true; + } + + public void setUseCurrentNodePositions(boolean b) {} + + + // + private static String[] options={"Padding"}; + private static String[] descriptions={"Minimum space between nodes"}; + private static Class[] optionclass={String.class}; + private static Validator paddingValidator=new IntStringValidator(0,1000); + + public String[] getOptionKeys() { + return options; + } + + public boolean setOption(String key, Object value) { + //key equals "padding" + if(key!=null && key.equals(options[0])){ + if(paddingValidator.validate(value)){ + PADDING=Integer.parseInt((String)value); + return true; + } + } + return false; + } + + public Validator getOptionValidator(String key) { + //key equals "padding" + if(key!=null && key.equals(options[0])){ + return paddingValidator; + } + return null; + } + + public String getOptionDescription(String key) { + if (key==null) return null; + for(int i=0; (i < descriptions.length) && (i < options.length); i++){ + if(key.equals(options[i])){ + return descriptions[i]; + } + } + return null; + } + + public Object getOption(String key) { + //key equals "padding" + if(key!=null && key.equals(options[0])){ + return String.valueOf(PADDING); + } + return null; + } + + public Class getOptionClass(String key) { + if (key==null) return null; + for(int i=0; (i < optionclass.length) && (i < options.length); i++){ + if(key.equals(options[i])){ + return optionclass[i]; + } + } + return null; + } + // + +} + diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/ExternalGraphLayoutWrapper.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/ExternalGraphLayoutWrapper.java new file mode 100644 index 000000000000..a7da269ff856 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/ExternalGraphLayoutWrapper.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.layout; + +import at.ssw.positionmanager.LayoutGraph; +import at.ssw.positionmanager.LayoutManager; +import at.ssw.dataflow.options.Validator; + +/** + * Wrapper class for LayoutManager implementations that do not support the + * ExternalGraphLayouter Interface used within the project. + * This class can be used to test implementations of the LayoutManager interface + * on data-flow graphs. In advanved cases it is better to implement the + * ExternalGraphLayouter interface because then the full features of the + * optionprovider are released. + * + * @author Stefan Loidl + */ +public class ExternalGraphLayoutWrapper implements ExternalGraphLayouter{ + + private boolean clustering=false; + private LayoutManager layout=null; + private boolean movement=false; + private boolean animation=false; + + /** + * Creates a new instance of ExternalGraphLayoutWrapper + * layout: the LayoutManager + * clustering: is clustering supported? + * movement: is node movement supported? + * animation: is node animation supported? + * + * Note: Movement is supported if the routing can be done indepenent from + * the layout step. + * Note: Node animation is supported if the routing consumes low time. + */ + public ExternalGraphLayoutWrapper(LayoutManager layout, boolean clustering, boolean movement, boolean animation) { + this.clustering=clustering; + this.layout=layout; + this.animation=animation; + this.movement=movement; + } + + public boolean isClusteringSupported() { + return clustering; + } + + public void doLayout(LayoutGraph graph) { + if(layout!=null) layout.doLayout(graph); + } + + public void doRouting(LayoutGraph graph) { + if(layout!=null) layout.doRouting(graph); + } + + + public String[] getOptionKeys() { + return new String[0]; + } + + public boolean setOption(String key, Object value) { + return false; + } + + public Object getOption(String key) { + return null; + } + + public Class getOptionClass(String key) { + return null; + } + + public Validator getOptionValidator(String key) { + return null; + } + + public String getOptionDescription(String key) { + return null; + } + + public boolean isAnimationSupported() { + return animation; + } + + public boolean isMovementSupported() { + return movement; + } + + public void setUseCurrentNodePositions(boolean b) {} + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/ExternalGraphLayouter.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/ExternalGraphLayouter.java new file mode 100644 index 000000000000..a7cf5984a5c3 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/ExternalGraphLayouter.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.layout; + +import at.ssw.positionmanager.LayoutManager; +import at.ssw.dataflow.options.OptionProvider; + +/** + * Extended Interface for LayoutManager supporting options and advanced + * features like clustering and movemement. + * + * @author Stefan Loidl + */ +public interface ExternalGraphLayouter extends OptionProvider, LayoutManager{ + + /** Does the algorithm support clustering? */ + public boolean isClusteringSupported(); + /** Is the routing algorithms fast enough to handle an animation task? */ + public boolean isAnimationSupported(); + /** Is routing possible independet from the layout task? */ + public boolean isMovementSupported(); + + /**Defines if the layouter should build on the current node positions*/ + public void setUseCurrentNodePositions(boolean b); + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/ForceLayouter.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/ForceLayouter.java new file mode 100644 index 000000000000..10b448e13834 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/ForceLayouter.java @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.layout; + +import at.ssw.positionmanager.LayoutGraph; +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Vertex; +import at.ssw.visualizer.graphhelper.DiGraph; +import at.ssw.visualizer.graphhelper.Node; +import at.ssw.dataflow.options.BooleanStringValidator; +import at.ssw.dataflow.options.DoubleStringValidator; +import at.ssw.dataflow.options.IntStringValidator; +import at.ssw.dataflow.options.Validator; +import java.awt.Point; +import java.awt.geom.Point2D; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Random; + +/** + * This class implements a force-directed layout algorithm. It uses the well + * known spring model that relys on springs and electrical forces. + * + * @author Stefan Loidl + */ +public class ForceLayouter implements ExternalGraphLayouter{ + + //Parameters for the foce model + private double SPRINGLEN = 50; + private double STIFFNESS = 15; + private double REPULSION = 100; + //Parameters if one of the nodes is expanded + private double SPRINGLENEXP = 50; + private double STIFFNESSEXP = 15; + private double REPULSIONEXP = 300; + + //During one cycle one node may only move this distance + private double MAXIMUMMOVEMENT=500; + + //Space between the connected components + private int PADDING=30; + + //Iterations the algorithms uses. + private int ITERATIONS=150; + + //Seed for the random node positioning + private long SEED=133; + + //Use logarithmic spring model. The alternative is hookes law. + private boolean USELOGSPRINGS=false; + + //Reuse current node positions for further optimizations + //the alternative is to use random positions. + private boolean USECURRENTNODEPOSITIONS=false; + + //Current positions of the nodes + private Hashtable posList; + + /* Performs the layout cycles */ + public void doLayout(LayoutGraph graph) { + Hashtable idtoverticles=new Hashtable(); + Hashtable verticlestoid=new Hashtable(); + + DiGraph dg=new DiGraph(); + Iterator iter=graph.getVertices().iterator(); + + //Add Nodes to Graphhelper + int i=0; + while(iter.hasNext()){ + String id=String.valueOf(i); + dg.addNode(new at.ssw.visualizer.graphhelper.Node(id)); + Vertex v=iter.next(); + + idtoverticles.put(id,v); + verticlestoid.put(v,id); + i++; + } + + //Add Edges to Graphhelper + for(Link l: graph.getLinks()){ + String from=verticlestoid.get(l.getFrom().getVertex()); + String to=verticlestoid.get(l.getTo().getVertex()); + + dg.addEdge(new at.ssw.visualizer.graphhelper.Edge(dg.getNode(from),dg.getNode(to))); + } + + //Perform the layout + layout(graph,dg,idtoverticles,verticlestoid); + } + + /* Peforms the layout per connected component */ + private void layout(LayoutGraph lg, DiGraph digraph, Hashtable idtoverticles, Hashtable verticlestoid) { + int lastmax=0; + int max=0; + for(DiGraph dg:digraph.getConnectedComponents()){ + + //initialize positions with random numbers + posList=new Hashtable(); + Random rand=new Random(SEED); + for(Node w: dg.getNodes()){ + if(USECURRENTNODEPOSITIONS){ + Vertex v=idtoverticles.get(w.ID); + double x=0.0,y=0.0; + if(v!=null){ + x=v.getPosition().x; + y=v.getPosition().y; + } + posList.put(w.ID,new doublePoint(x,y)); + } + else posList.put(w.ID,new doublePoint(rand.nextDouble()*300d,rand.nextDouble()*300d)); + } + + //perform several relaxation iterations + for(int i=0; ip.getX()) smallestX=p.getX(); + if(smallestY>p.getY()) smallestY=p.getY(); + } + + max=0; + for(Node w: dg.getNodes()){ + Vertex v=idtoverticles.get(w.ID); + + Point2D p=posList.get(w.ID); + int x=((int)(p.getX()-smallestX))+lastmax+PADDING; + int y=((int)(p.getY()-smallestY))+PADDING; + + if(x+v.getSize().width >max) max=x+v.getSize().width; + v.setPosition(new Point(x, y)); + } + lastmax=max; + } + } + + + /* + * Calculates the forces on node n and moves it a small distance to lessen + * it. + */ + private void relaxation(Node n, DiGraph dg, Hashtable idtoverticles){ + double stiffness, repulsion, springlen; + + double X = posList.get(n.ID).getX(); + double Y = posList.get(n.ID).getY(); + boolean nExpanded=false; + Vertex v=idtoverticles.get(n.ID); + if(v!=null) nExpanded=v.isExpanded(); + + LinkedList adjacentVertices=new LinkedList(); + + // Get all adjacent Nodes + for(Node pre: n.pred){ + if(!n.ID.equals(pre.ID)) + adjacentVertices.add(pre); + } + for(Node succ: n.succ){ + if(!n.ID.equals(succ.ID)) + adjacentVertices.add(succ); + } + + //Calcualte Spring len between all adjacent Nodes + double SpringX = 0, SpringY = 0; + for(Node adjacent:adjacentVertices) { + //determine if one of the nodes is expanded + boolean expanded=nExpanded; + v=idtoverticles.get(adjacent.ID); + if(v!=null) expanded|=v.isExpanded(); + + if(expanded){ + stiffness=STIFFNESSEXP; + springlen=SPRINGLENEXP; + } + else{ + stiffness=STIFFNESS; + springlen=SPRINGLEN; + } + + double adjX = posList.get(adjacent.ID).getX(); + double adjY = posList.get(adjacent.ID).getY(); + + double distance = Point2D.distance( adjX, adjY, X, Y ); + //Minimum distance between nodes! + if(distance == 0) distance = 0.01d; + + if(USELOGSPRINGS){ + //Logarithmic Springs + SpringX +=stiffness*Math.log(distance/springlen)*((X-adjX)/distance); + SpringY +=stiffness*Math.log(distance/springlen)*((Y-adjY)/distance); + } + else{ + //Hookes Law with relativ springkonstant + SpringX +=stiffness*((distance-springlen)/(2*springlen)) *((X-adjX)/distance); + SpringY +=stiffness*((distance-springlen)/(2*springlen)) *((Y-adjY)/distance); + } + } + + //Calcualte Repulsion + double RepulsionX = 0, RepulsionY = 0; + for(Node w: dg.getNodes()) { + if(w == n) continue; + + //determine if one of the nodes is expanded + boolean expanded=nExpanded; + v=idtoverticles.get(w.ID); + if(v!=null) expanded|=v.isExpanded(); + + if(expanded) repulsion=REPULSIONEXP; + else repulsion=REPULSION; + + double nX = posList.get(w.ID).getX(); + double nY = posList.get(w.ID).getY(); + + double distance = Point2D.distance( nX, nY, X, Y ); + if(distance == 0) distance = 0.01d; + + //If Spring energy is positiv- this one is negativ + RepulsionX -= (repulsion/distance)*((X-nX)/distance); + RepulsionY -= (repulsion/distance)*((Y-nY)/distance); + + } + + // Move Node in direction of the force + double dx = -(SpringX + RepulsionX); + double dy = -(SpringY + RepulsionY); + + //Make shure the node moves not too far off the others + if(dx>MAXIMUMMOVEMENT) dx=MAXIMUMMOVEMENT; + if(dx*-1>MAXIMUMMOVEMENT) dx=-MAXIMUMMOVEMENT; + if(dy>MAXIMUMMOVEMENT) dy=MAXIMUMMOVEMENT; + if(dy*-1>MAXIMUMMOVEMENT) dy=-MAXIMUMMOVEMENT; + + Point2D p=posList.get(n.ID); + p.setLocation(p.getX()+dx,p.getY()+dy); + } + + + /* Performs the routing using a simple direct-line router */ + public void doRouting(LayoutGraph graph) { + RoutingHelper.doRouting(graph); + } + + + public boolean isClusteringSupported() { + return false; + } + + + public boolean isAnimationSupported() { + return true; + } + + + public boolean isMovementSupported() { + return true; + } + + + public void setUseCurrentNodePositions(boolean b) { + USECURRENTNODEPOSITIONS=b; + } + + + // + private static String[] options={"Componentpadding", "Springlength","Expanded Springlength", "Stiffness","Expanded Stiffness", "Iterations", "Repulsion","Expanded Repulsion", "Seed", "Log. Springs", "Maximum Displacement"}; + private static String[] descriptions={"Minimum space between connected components", + "Length of the spring between nodes","Length of the spring between expanded nodes", "Stiffness of the spring between the nodes", + "Stiffness of the spring between expanded nodes","Relaxation iterations the algorithm performs", "Standard repulsion of a unexpanded Node", "Standard repulsion of a expanded Node", + "Seed for the random prepositioning", "Logarithmic spring simulation is used?", "Maximum displacement in x and y direction during relaxation."}; + private static Class[] optionclass={String.class,String.class,String.class, String.class,String.class,String.class,String.class,String.class}; + private static Validator[] validators={ + new IntStringValidator(0,1000), //padding + new DoubleStringValidator(0.0d,1000.0d), //Springlen + new DoubleStringValidator(0.0d,1000.0d), //expanded Springlen + new DoubleStringValidator(0.0d,1000.0d), //Stiffness + new DoubleStringValidator(0.0d,1000.0d), //expanded Stiffness + new IntStringValidator(0,5000), //Iterations + new DoubleStringValidator(0.0d,10000.0d), //Repulsion + new DoubleStringValidator(0.0d,10000.0d), //expanded Repulsion + new IntStringValidator(0,Integer.MAX_VALUE), //Seed + new BooleanStringValidator(), //Log Spring + new DoubleStringValidator(0.0d,5000.0d) //max. displacement + }; + + + public String[] getOptionKeys() { + return options; + } + + public boolean setOption(String key, Object value) { + if(key==null) return false; + //key equals "Componentpadding" + if(key.equals(options[0])){ + if(validators[0].validate(value)){ + PADDING=Integer.parseInt((String)value); + return true; + } + } + else if(key.equals(options[1])){ + if(validators[1].validate(value)){ + SPRINGLEN=Double.parseDouble((String)value); + return true; + } + } + else if(key.equals(options[2])){ + if(validators[2].validate(value)){ + SPRINGLENEXP=Double.parseDouble((String)value); + return true; + } + } + else if(key.equals(options[3])){ + if(validators[3].validate(value)){ + STIFFNESS=Double.parseDouble((String)value); + return true; + } + } + else if(key.equals(options[4])){ + if(validators[4].validate(value)){ + STIFFNESSEXP=Double.parseDouble((String)value); + return true; + } + } + else if(key.equals(options[5])){ + if(validators[5].validate(value)){ + ITERATIONS=Integer.parseInt((String)value); + return true; + } + } + else if(key.equals(options[6])){ + if(validators[6].validate(value)){ + REPULSION=Double.parseDouble((String)value); + return true; + } + } + else if(key.equals(options[7])){ + if(validators[7].validate(value)){ + REPULSIONEXP=Double.parseDouble((String)value); + return true; + } + } + else if(key.equals(options[8])){ + if(validators[8].validate(value)){ + SEED=Integer.parseInt((String)value); + return true; + } + } + else if(key.equals(options[9])){ + if(validators[9].validate(value)){ + USELOGSPRINGS=Boolean.parseBoolean((String)value); + return true; + } + } + else if(key.equals(options[10])){ + if(validators[10].validate(value)){ + MAXIMUMMOVEMENT=Double.parseDouble((String)value); + return true; + } + } + return false; + } + + + public Object getOption(String key) { + if(key==null) return null; + //key equals "Componentpadding" + if(key.equals(options[0])){ + return String.valueOf(PADDING); + } + //key equals "Springlength" + else if(key.equals(options[1])){ + return String.valueOf(SPRINGLEN); + } + //key equals "Expanded Springlength" + else if(key.equals(options[2])){ + return String.valueOf(SPRINGLENEXP); + } + //key equals "Stiffness" + else if(key.equals(options[3])){ + return String.valueOf(STIFFNESS); + } + //key equals "Expanded Stiffness" + else if(key.equals(options[4])){ + return String.valueOf(STIFFNESSEXP); + } + //key equals "Iterations" + else if(key.equals(options[5])){ + return String.valueOf(ITERATIONS); + } + //key equals "Repulsion" + else if(key.equals(options[6])){ + return String.valueOf(REPULSION); + } + //key equals "Repulsion" + else if(key.equals(options[7])){ + return String.valueOf(REPULSIONEXP); + } + //key equals "SEED" + else if(key.equals(options[8])){ + return String.valueOf(SEED); + } + //key equals "Log Springs" + else if(key.equals(options[9])){ + return String.valueOf(USELOGSPRINGS); + } + //key equals "Maximum Displacement" + else if(key.equals(options[10])){ + return String.valueOf(MAXIMUMMOVEMENT); + } + + return null; + } + + + public String getOptionDescription(String key) { + if (key==null) return null; + for(int i=0; (i < descriptions.length) && (i < options.length); i++){ + if(key.equals(options[i])){ + return descriptions[i]; + } + } + return null; + } + + + public Validator getOptionValidator(String key) { + if (key==null) return null; + for(int i=0; (i < validators.length) && (i < options.length); i++){ + if(key.equals(options[i])){ + return validators[i]; + } + } + return null; + } + + + + public Class getOptionClass(String key) { + if (key==null) return null; + for(int i=0; (i < optionclass.length) && (i < options.length); i++){ + if(key.equals(options[i])){ + return optionclass[i]; + } + } + return null; + } + // + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/HierarchicalNodesLayouter.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/HierarchicalNodesLayouter.java new file mode 100644 index 000000000000..df8c6a11fb21 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/HierarchicalNodesLayouter.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.layout; + +import at.ssw.positionmanager.LayoutGraph; +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Vertex; +import at.ssw.visualizer.graphhelper.DiGraph; +import at.ssw.dataflow.options.IntStringValidator; +import at.ssw.dataflow.options.Validator; +import java.awt.Point; +import java.util.Hashtable; +import java.util.Iterator; +import org.eclipse.draw2d.geometry.Insets; +import org.eclipse.draw2d.graph.DirectedGraph; +import org.eclipse.draw2d.graph.DirectedGraphLayout; +import org.eclipse.draw2d.graph.Edge; +import org.eclipse.draw2d.graph.Node; + +/** + * Node Layout using Hirachical Layout from GEF draw2d library. + * This class is a wrapper that transforms the input graph models. + * + * @author Stefan Loidl + */ +public class HierarchicalNodesLayouter implements ExternalGraphLayouter{ + + //Inset of the graph in the overall painting area + private static final int TOP_BORDER = 20; + private static final int LEFT_BORDER = 20; + + //Padding of the nodes and the connected components + private int PADDING = 30; + + /* + * Performs the layout task via transformation of the LayoutGraph-model + * to that of GEF. Afterwards the algorithm is simply applied. + */ + public void doLayout(LayoutGraph graph) { + Hashtable idtoverticles=new Hashtable(); + Hashtable verticlestoid=new Hashtable(); + + Hashtable d2dNodes=new Hashtable(); + + + DiGraph dg=new DiGraph(); + Iterator iter=graph.getVertices().iterator(); + + //Add Nodes to Graphhelper + int i=0; + while(iter.hasNext()){ + String id=String.valueOf(i); + dg.addNode(new at.ssw.visualizer.graphhelper.Node(id)); + Vertex v=iter.next(); + + idtoverticles.put(id,v); + verticlestoid.put(v,id); + i++; + } + + //Add Edges to Graphhelper + for(Link l: graph.getLinks()){ + String from=verticlestoid.get(l.getFrom().getVertex()); + String to=verticlestoid.get(l.getTo().getVertex()); + + dg.addEdge(new at.ssw.visualizer.graphhelper.Edge(dg.getNode(from),dg.getNode(to))); + } + + int max=0, lastmax=0; + //Layouting for each connected component + for(DiGraph g:dg.getConnectedComponents()){ + DirectedGraph d2dGraph=new DirectedGraph(); + //Add Nodes to Draw2D DirectedGraph + for(at.ssw.visualizer.graphhelper.Node n:g.getNodes()){ + Vertex v=idtoverticles.get(n.ID); + Node node=new Node(); + node.data=v; + node.width = v.getSize().width; + node.height = v.getSize().height; + node.setPadding(new Insets(PADDING, PADDING, PADDING, PADDING)); + d2dGraph.nodes.add(node); + d2dNodes.put(n.ID,node); + } + + //Add Edges to Draw2D DirectedGraph + for(at.ssw.visualizer.graphhelper.Edge e:g.getEdges()){ + Edge edge=new Edge(d2dNodes.get(e.source.ID),d2dNodes.get(e.destination.ID)); + d2dGraph.edges.add(edge); + } + + //Perform Layouting step + DirectedGraphLayout layout = new DirectedGraphLayout(); + layout.visit(d2dGraph); + + //Calculate actual positions + lastmax+=max+PADDING; + max=0; + for (int j = 0; j < d2dGraph.nodes.size(); j++) { + Node n = d2dGraph.nodes.getNode(j); + assert n.data != null; + Vertex v = (Vertex)n.data; + + Point p = new Point(lastmax+ n.x + LEFT_BORDER, n.y + TOP_BORDER); + + int width=v.getSize().width; + if(n.x+width > max) max=n.x+width; + + v.setPosition(p); + } + } + } + + /* Performs the routing using a simple direct-line router */ + public void doRouting(LayoutGraph graph) { + RoutingHelper.doRouting(graph); + } + + public boolean isClusteringSupported() { + return false; + } + + public boolean isAnimationSupported() { + return true; + } + + public boolean isMovementSupported() { + return true; + } + + public void setUseCurrentNodePositions(boolean b) { + } + + // + private static String[] options={"Padding"}; + private static String[] descriptions={"Minimum space between nodes"}; + private static Class[] optionclass={String.class}; + private static Validator paddingValidator=new IntStringValidator(0,1000); + + public String[] getOptionKeys() { + return options; + } + + public boolean setOption(String key, Object value) { + //key equals "padding" + if(key!=null && key.equals(options[0])){ + if(paddingValidator.validate(value)){ + PADDING=Integer.parseInt((String)value); + return true; + } + } + return false; + } + + public Validator getOptionValidator(String key) { + //key equals "padding" + if(key!=null && key.equals(options[0])){ + return paddingValidator; + } + return null; + } + + public String getOptionDescription(String key) { + if (key==null) return null; + for(int i=0; (i < descriptions.length) && (i < options.length); i++){ + if(key.equals(options[i])){ + return descriptions[i]; + } + } + return null; + } + + public Object getOption(String key) { + //key equals "padding" + if(key!=null && key.equals(options[0])){ + return String.valueOf(PADDING); + } + return null; + } + + public Class getOptionClass(String key) { + if (key==null) return null; + for(int i=0; (i < optionclass.length) && (i < options.length); i++){ + if(key.equals(options[i])){ + return optionclass[i]; + } + } + return null; + } + // +} + diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/MagneticSpringForceLayouter.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/MagneticSpringForceLayouter.java new file mode 100644 index 000000000000..35eb5e39155b --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/MagneticSpringForceLayouter.java @@ -0,0 +1,477 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.layout; + +import at.ssw.positionmanager.LayoutGraph; +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Vertex; +import at.ssw.visualizer.graphhelper.DiGraph; +import at.ssw.visualizer.graphhelper.Node; +import at.ssw.dataflow.options.BooleanStringValidator; +import at.ssw.dataflow.options.DoubleStringValidator; +import at.ssw.dataflow.options.IntStringValidator; +import at.ssw.dataflow.options.Validator; +import java.awt.Point; +import java.awt.geom.Point2D; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Random; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * This class implements a force-directed layout building on the well known + * spring-model. To introduce direction to the graph-layout the magnetic + * spring model descibed in the following work was used: + * + * Kozo Sugiyama, Kazuo Misue: Graph Drawing by Magnetic-Spring Model; + * Technical Report, 1994. ISIS-RR-94-14E. + * + * @author Stefan Loidl + */ +public class MagneticSpringForceLayouter implements ExternalGraphLayouter{ + + //Parameters for the foce model + private double SPRINGLEN = 50; + private double STIFFNESS = 15; + private double REPULSION = 100; + + //Parameters if one of the nodes is expanded + private double SPRINGLENEXP = 50; + private double STIFFNESSEXP = 15; + private double REPULSIONEXP = 250; + + //Strength of the parallel vertical field + private double FIELDSTRENGTH=0.05; + + //During one cycle one node may only move this distance + private double MAXIMUMMOVEMENT=500; + + //Space between the connected components + private int PADDING=30; + + //Iterations the algorithms uses. + private int ITERATIONS=300; + + //Seed for the random node positioning + private long SEED=133; + + //Use logarithmic spring model. The alternative is hookes law. + private boolean USELOGSPRINGS=false; + + //Reuse current node positions for further optimizations + //the alternative is to use random positions. + private boolean USECURRENTNODEPOSITIONS=false; + + //length of the area the initial layout is done on + private double INITIALLAYOUTSIDE=3000d; + private double LAYERHEIGHT=100d; + + //Current positions of the nodes + private Hashtable posList; + + + /* Performs the layout algorithm */ + public void doLayout(LayoutGraph graph) { + Hashtable idtoverticles=new Hashtable(); + Hashtable verticlestoid=new Hashtable(); + + DiGraph dg=new DiGraph(); + Iterator iter=graph.getVertices().iterator(); + + //Add Nodes to Graphhelper + int i=0; + while(iter.hasNext()){ + String id=String.valueOf(i); + dg.addNode(new at.ssw.visualizer.graphhelper.Node(id)); + Vertex v=iter.next(); + + idtoverticles.put(id,v); + verticlestoid.put(v,id); + i++; + } + + //Add Edges to Graphhelper + for(Link l: graph.getLinks()){ + String from=verticlestoid.get(l.getFrom().getVertex()); + String to=verticlestoid.get(l.getTo().getVertex()); + + dg.addEdge(new at.ssw.visualizer.graphhelper.Edge(dg.getNode(from),dg.getNode(to))); + } + + layout(graph,dg,idtoverticles,verticlestoid); + } + + + /* + * Performs an initial layout step based on the breadth first search graph + * traversial. + */ + private void BFSInitialAssignment(LinkedList fringe, HashSet assigned, double layer){ + if(fringe==null || fringe.size()==0) return; + assigned.addAll(fringe); + + LinkedList newFringe=new LinkedList(); + double x=0.0, deltaX=INITIALLAYOUTSIDE/(fringe.size()+1); + + for(Node n: fringe){ + x+=deltaX; + posList.put(n.ID,new doublePoint(x,layer)); + for(Node s:n.succ) if(!assigned.contains(s)) newFringe.add(s); + } + BFSInitialAssignment(newFringe,assigned,layer+LAYERHEIGHT); + } + + /* + * Performs the layout task. Note that three differnent approaches for + * the initial positioning of nodes are implemented. One possibility is + * to use the currnet node positions without change. The second is to use + * a random initial layout. And the third one is to use the BFS positioning + * that introduces a pre-layering step. + */ + private void layout(LayoutGraph lg, DiGraph digraph, Hashtable idtoverticles, Hashtable verticlestoid) { + int lastmax=0; + int max=0; + for(DiGraph dg:digraph.getConnectedComponents()){ + + //initialize positions with random numbers + posList=new Hashtable(); + Random rand=new Random(133); + + //reuse current position + if(USECURRENTNODEPOSITIONS){ + for(Node w: dg.getNodes()){ + Vertex v=idtoverticles.get(w.ID); + double x=0.0,y=0.0; + if(v!=null){ + x=v.getPosition().x; + y=v.getPosition().y; + } + posList.put(w.ID,new doublePoint(x,y)); + } + + } + //Breadth first search layer positioning + else{ + HashSet assigned=new HashSet(); + LinkedList fringe=new LinkedList(); + + for(Vertex v: new TreeSet(lg.findRootVertices())){ + Node n=dg.getNode(verticlestoid.get(v)); + if(n!=null) fringe.add(n); + } + BFSInitialAssignment(fringe,assigned,LAYERHEIGHT); + for(Node n:dg.getNodes()){ + if(!assigned.contains(n)) + posList.put(n.ID,new doublePoint(rand.nextDouble()*INITIALLAYOUTSIDE,rand.nextDouble()*INITIALLAYOUTSIDE)); + } + } + + //perform several relaxation iterations + for(int i=0; ip.getX()) smallestX=p.getX(); + if(smallestY>p.getY()) smallestY=p.getY(); + } + + max=0; + for(Node w: dg.getNodes()){ + Vertex v=idtoverticles.get(w.ID); + + Point2D p=posList.get(w.ID); + int x=((int)(p.getX()-smallestX))+lastmax+PADDING; + int y=((int)(p.getY()-smallestY))+PADDING; + + if(x+v.getSize().width >max) max=x+v.getSize().width; + v.setPosition(new Point(x, y)); + } + lastmax=max; + } + } + + + /* + * Calculates the forces on node n and moves it a small distance to lessen + * it. The magnetical field introduces a radial force on the edges. + * This influence is simplified and modeled as vertical force pointing + * downwards. + */ + public void relaxation(Node n, DiGraph dg, Hashtable idtoverticles){ + double stiffness, repulsion, springlen; + double X = posList.get(n.ID).getX(); + double Y = posList.get(n.ID).getY(); + boolean nExpanded=false; + Vertex v=idtoverticles.get(n.ID); + + if(v!=null) nExpanded=v.isExpanded(); + + LinkedList adjacentVertices=new LinkedList(); + + // Get all adjacent Nodes + for(Node pre: n.pred){ + if(!n.ID.equals(pre.ID)) + adjacentVertices.add(pre); + } + for(Node succ: n.succ){ + if(!n.ID.equals(succ.ID)) + adjacentVertices.add(succ); + } + + //Calcualte Spring len between all adjacent Nodes + double SpringX = 0, SpringY = 0; + for(Node adjacent:adjacentVertices) { + //determine if one of the nodes is expanded + boolean expanded=nExpanded; + v=idtoverticles.get(adjacent.ID); + if(v!=null) expanded|=v.isExpanded(); + + if(expanded){ + stiffness=STIFFNESSEXP; + springlen=SPRINGLENEXP; + } + else{ + stiffness=STIFFNESS; + springlen=SPRINGLEN; + } + + double adjX = posList.get(adjacent.ID).getX(); + double adjY = posList.get(adjacent.ID).getY(); + + double distance = Point2D.distance( adjX, adjY, X, Y ); + //Minimum distance between nodes! + if(distance == 0) distance = 0.01d; + + //Calculate the angle beween the directed spring and the vertical magnetic field + //via the angle between the vector resulting from the positions (vX,vY) and + //the vector (0,1). angle=arcCos( (0*vX+1*vY)/(len(vX,vY)*len(0,1)) ) + //This only applies if the current node is the endpoint of the spring + if(n.pred.contains(adjacent) && !n.succ.contains(adjacent)){ + double vX=X-adjX; + double vY=Y-adjY; + double angle=Math.acos((vY)/Point2D.distance(0,0,vX,vY)); + SpringY-=Math.abs(angle)*distance*FIELDSTRENGTH; + } + + + if(USELOGSPRINGS){ + //Logarithmic Springs + SpringX +=stiffness*Math.log(distance/springlen)*((X-adjX)/distance); + SpringY +=stiffness*Math.log(distance/springlen)*((Y-adjY)/distance); + } + else{ + //Hookes Law with relativ springkonstant + SpringX +=stiffness*((distance-springlen)/(2*springlen)) *((X-adjX)/distance); + SpringY +=stiffness*((distance-springlen)/(2*springlen)) *((Y-adjY)/distance); + } + } + + //Calcualte Repulsion + double RepulsionX = 0, RepulsionY = 0; + for(Node w: dg.getNodes()) { + if(w == n) continue; + + //determine if one of the nodes is expanded + boolean expanded=nExpanded; + v=idtoverticles.get(w.ID); + if(v!=null) expanded|=v.isExpanded(); + + if(expanded) repulsion=REPULSIONEXP; + else repulsion=REPULSION; + + double nX = posList.get(w.ID).getX(); + double nY = posList.get(w.ID).getY(); + + double distance = Point2D.distance( nX, nY, X, Y ); + if(distance == 0) distance = 0.01d; + + //If Spring energy is positiv- this one is negativ + RepulsionX -= (repulsion/distance)*((X-nX)/distance); + RepulsionY -= (repulsion/distance)*((Y-nY)/distance); + } + + // Move Node in direction of the force + double dx = -(SpringX + RepulsionX); + double dy = -(SpringY + RepulsionY); + + //Make shure the node moves not too far off the others + if(dx>MAXIMUMMOVEMENT) dx=MAXIMUMMOVEMENT; + if(dx*-1>MAXIMUMMOVEMENT) dx=-MAXIMUMMOVEMENT; + if(dy>MAXIMUMMOVEMENT) dy=MAXIMUMMOVEMENT; + if(dy*-1>MAXIMUMMOVEMENT) dy=-MAXIMUMMOVEMENT; + + Point2D p=posList.get(n.ID); + p.setLocation(p.getX()+dx,p.getY()+dy); + } + + + + /* Performs the routing using a simple direct-line router */ + public void doRouting(LayoutGraph graph) { + RoutingHelper.doRouting(graph); + } + + public boolean isClusteringSupported() { + return false; + } + + public boolean isAnimationSupported() { + return true; + } + + public boolean isMovementSupported() { + return true; + } + + public void setUseCurrentNodePositions(boolean b) { + USECURRENTNODEPOSITIONS=b; + } + + // + private static String[] options={"Componentpadding", "Springlength","Expanded Springlength", "Stiffness","Expanded Stiffness","Field Strength", "Iterations", "Repulsion","Expanded Repulsion", "Log. Springs", "Maximum Displacement"}; + private static String[] descriptions={"Minimum space between connected components", + "Length of the spring between nodes","Length of the spring between expanded nodes", "Stiffness of the spring between the nodes", + "Stiffness of the spring between expanded nodes","Strength of the vertical parallel field", "Relaxation iterations the algorithm performs", "Standard repulsion of a unexpanded Node", "Standard repulsion of a expanded Node", + "Logarithmic spring simulation is used?", "Maximum displacement in x and y direction during relaxation."}; + private static Class[] optionclass={String.class,String.class,String.class, String.class,String.class,String.class,String.class,String.class}; + private static Validator[] validators={ + new IntStringValidator(0,1000), //padding + new DoubleStringValidator(0.0d,1000.0d), //Springlen + new DoubleStringValidator(0.0d,1000.0d), //expanded Springlen + new DoubleStringValidator(0.0d,1000.0d), //Stiffness + new DoubleStringValidator(0.0d,1000.0d), //expanded Stiffness + new DoubleStringValidator(0.0d,10.0d), //field strength + new IntStringValidator(0,5000), //Iterations + new DoubleStringValidator(0.0d,10000.0d), //Repulsion + new DoubleStringValidator(0.0d,10000.0d), //expanded Repulsion + new BooleanStringValidator(), //Log Spring + new DoubleStringValidator(0.0d,5000.0d) //max. displacement + }; + + + public String[] getOptionKeys() { + return options; + } + + public int getIndexForKey(String key){ + for(int i=0;i + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/RoutingHelper.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/RoutingHelper.java new file mode 100644 index 000000000000..038309a2350c --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/RoutingHelper.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.layout; + +import at.ssw.positionmanager.LayoutGraph; +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Port; +import at.ssw.positionmanager.Vertex; +import java.awt.Point; +import java.util.LinkedList; + +/** + * This class is a standard implementation of a direct routing algorithm, making it + * possible to seperate two lines between the same two nodes. + * + * @author Stefan Loidl + */ +public class RoutingHelper { + + //This value is needed because of the definition of inset values- outranging the + //position values of the original widget- if no inset is used- value is 0. + private static final int BORDERINSETCORRECT=0; + //Space between two twins lines + private static final int SEPERATETWINLINES=5; + + /** + * Does the routing for the graph. + */ + public static void doRouting(LayoutGraph graph){ + if(graph==null) return; + for(Link l: graph.getLinks()){ + boolean twin=false; + Vertex from=l.getFrom().getVertex(); + Vertex to=l.getTo().getVertex(); + + if(!(from.isDirty() || to.isDirty())) continue; + + //Search for twin link (in diffent direction!) + for(Port p:graph.getInputPorts(from)){ + for(Link l2: graph.getPortLinks(p)){ + if(l2.getFrom().getVertex()==l.getTo().getVertex()) twin=true; + } + } + + routeLink(l,twin); + } + } + + /** + * Does the routing for the link l. The boolean defines if a second link exists connecting + * the two verticles. + */ + private static void routeLink(Link l, boolean hasTwin){ + Vertex v1=l.getFrom().getVertex(); + Vertex v2=l.getTo().getVertex(); + Point p1=(Point)v1.getPosition().clone(); + Point p2=(Point)v2.getPosition().clone(); + + //Translate to the center + p1.translate(-BORDERINSETCORRECT+ v1.getSize().width/2,-BORDERINSETCORRECT+ v1.getSize().height/2); + p2.translate(-BORDERINSETCORRECT+ v2.getSize().width/2, -BORDERINSETCORRECT+ v2.getSize().height/2); + + + //Handle the six cases possible: + //1+2. x- Values are identical + if(p1.x==p2.x){ + int shift=0; + if(p1.y>p2.y){ + if(hasTwin) shift=SEPERATETWINLINES; + p1.translate(shift,-v1.getSize().height/2); + p2.translate(shift,v2.getSize().height/2); + } + else{ + if(hasTwin) shift=-SEPERATETWINLINES; + p1.translate(shift,v1.getSize().height/2); + p2.translate(shift,-v2.getSize().height/2); + } + } + else{ + //gradient of the line + double k=((double)(p1.y-p2.y))/(p1.x-p2.x); + double gk= Math.atan(k); + double twdx=0, twdy=0; + + //If x value of p1 <= p2 then the two are + //simply swapped. + boolean swap=false; + Point p3; + Vertex v3; + + if(hasTwin){ + twdx=Math.abs(Math.sin(gk)*SEPERATETWINLINES); + twdy=Math.abs(Math.cos(gk)*SEPERATETWINLINES); + } + + if(p1.x <= p2.x){ + swap=true; + p3=p1; p1=p2; p2=p3; + v3=v1; v1=v2; v2=v3; + twdx*=-1; twdy*=-1; + } + + if(p1.y>p2.y){ + //lower quarter with respect to p1 + p1.translate((int)twdx,-(int)twdy); + p2.translate((int)twdx,-(int)twdy); + + double x=((double)v2.getSize().height/2.0)/Math.tan(gk); + if(Math.abs(x)<=v2.getSize().width/2){ + p2.translate((int)x,v2.getSize().height/2); + } else{ + x=(Math.tan(gk)*v2.getSize().width)/2.0; + p2.translate(v2.getSize().width/2,(int)x); + } + + x=(Math.tan(Math.PI/2-gk)*v1.getSize().height)/2.0; + if(Math.abs(x)<=v1.getSize().width/2){ + p1.translate(-(int)x,-v1.getSize().height/2); + } else{ + x=((double)(v1.getSize().width)/2.0)/Math.tan(Math.PI/2-gk); + p1.translate(-v1.getSize().width/2,-(int)x); + } + + }else{ + //upper quarter with respect to p1 + p1.translate((int)-twdx,(int)-twdy); + p2.translate((int)-twdx,(int)-twdy); + + double x=((double)v1.getSize().height/2.0)/Math.tan(gk); + if(Math.abs(x)<=v1.getSize().width/2){ + p1.translate((int)x,v1.getSize().height/2); + } else{ + x=(Math.tan(gk)*v1.getSize().width)/2; + p1.translate(-v1.getSize().width/2,-(int)x); + } + + x=(Math.tan(Math.PI/2-gk)*v2.getSize().height)/2.0; + if(Math.abs(x)<=v2.getSize().width/2){ + p2.translate(-(int)x,-v2.getSize().height/2); + } else{ + x=((double)(v2.getSize().width)/2.0)/Math.tan(Math.PI/2-gk); + p2.translate(v2.getSize().width/2,(int)x); + } + } + if(swap){ + p3=p1; p1=p2; p2=p3; + v3=v1; v1=v2; v2=v3; + } + + + } + + LinkedList cp=new LinkedList(); + cp.add(p1); + cp.add(p2); + l.setControlPoints(cp); + } + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/doublePoint.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/doublePoint.java new file mode 100644 index 000000000000..3e772741d6ce --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/layout/doublePoint.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.layout; + +import java.awt.geom.Point2D; + +/** + * Simple helper class for the force-directed layouts. + * + * @author Stefan Loidl + */ + +class doublePoint extends Point2D{ + private double x,y; + + public doublePoint(double x, double y){this.x=x; this.y=y;} + + public double getX() {return x;} + + public double getY() {return y;} + + public void setLocation(double x, double y) {this.x=x; this.y=y;} +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/BooleanStringValidator.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/BooleanStringValidator.java new file mode 100644 index 000000000000..0beed4e17000 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/BooleanStringValidator.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.options; + +/** + * Validates if the option is parsable as Boolean + * + * @author Stefan Loidl + */ +public class BooleanStringValidator implements Validator{ + + private String error=null; + + public boolean validate(Object option) { + try{ + Boolean.parseBoolean((String)option); + return true; + }catch(Exception e){ + error="Option not parseable as Boolean"; + return false; + } + } + + public String getLastErrorMessage() { + return error; + } + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/DoubleStringValidator.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/DoubleStringValidator.java new file mode 100644 index 000000000000..511ee55d1970 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/DoubleStringValidator.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.options; + +/** + * Validates if the string option is parsable as double. + * Optinally min and max value can be given. + * + * @author Stefan Loidl + */ +public class DoubleStringValidator implements Validator{ + + private double min, max; + String error=null; + + /** + * Creates a new instance of IntValidator with min and max bound + * for the option. + */ + public DoubleStringValidator(double min, double max) { + this.min=min; + this.max=max; + } + + /** + * Creates a new instance of the validator without bounds. + */ + public DoubleStringValidator(){ + this(Double.MIN_VALUE,Double.MAX_VALUE); + } + + public boolean validate(Object option) { + try{ + if(!(option instanceof String)){ + error="Option is not a string"; + return false; + } + double x=Double.parseDouble((String)option); + if(x>=min && x <=max) { + error=null; + return true; + } + error="Value not within intervall: ["+min+","+max+"]"; + return false; + }catch(Exception e){ + error="No double value."; + return false; + } + } + + + public String getLastErrorMessage() { + return error; + } + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/IntStringValidator.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/IntStringValidator.java new file mode 100644 index 000000000000..532dc3d3122e --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/IntStringValidator.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.options; + +/** + * Validates if the option given as string is parsable as integer. + * Optinally min and max value can be given. + * + * @author Stefan Loidl + */ +public class IntStringValidator implements Validator{ + + private int min, max; + String error=null; + + /** + * Creates a new instance of IntValidator with min and max bound + * for the option. + */ + public IntStringValidator(int min, int max) { + this.min=min; + this.max=max; + } + + /** + * Creates a new instance of the validator without bounds. + */ + public IntStringValidator(){ + this(Integer.MIN_VALUE,Integer.MAX_VALUE); + } + + public boolean validate(Object option) { + try{ + if(!(option instanceof String)){ + error="Option is not a string"; + return false; + } + int x=Integer.parseInt((String)option); + if(x>=min && x <=max) { + error=null; + return true; + } + error="Value not within intervall: ["+min+","+max+"]"; + return false; + }catch(Exception e){ + error="No integer value."; + return false; + } + } + + public String getLastErrorMessage() { + return error; + } + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/OptionEditor.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/OptionEditor.java new file mode 100644 index 000000000000..57d82eb6130a --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/OptionEditor.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.options; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableModel; + +/** + * This class implements a dialog for the modification of options provided + * by an optionprovider. + * + * @author Stefan Loidl + */ +public class OptionEditor extends JDialog { + private OptionProvider provider; + private static final String DEFAULTTITEL="Options"; + private static final String BUTCLOSE="Close"; + + /** Creates a new instance of OptionEditor */ + public OptionEditor(OptionProvider provider) { + getContentPane().setLayout(new BorderLayout()); + this.provider=provider; + setSize(300,300); + + //Center on screen + setLocationRelativeTo(null); + + setTitle(DEFAULTTITEL); + setModal(true); + JTable tab=new JTable(); + JLabel errorLabel=new JLabel(); + errorLabel.setHorizontalAlignment(JLabel.CENTER); + errorLabel.setForeground(Color.RED); + + + tab.setModel(new Model(provider,errorLabel)); + + + tab.setColumnSelectionAllowed(true); + + JScrollPane sp=new JScrollPane(tab); + getContentPane().add(sp,BorderLayout.CENTER); + + JPanel p=new JPanel(); + p.setLayout(new GridLayout(2,1)); + p.add(errorLabel); + + JButton b=new JButton(BUTCLOSE); + b.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e) { + OnClose(); + } + }); + p.add(b); + + getContentPane().add(p,BorderLayout.SOUTH); + } + + public void OnClose(){ + setVisible(false); + } + + + + protected class Model extends AbstractTableModel{ + private OptionProvider provider; + private final String[] TableHeader={"Option","Value"}; + private JLabel error; + + public Model(OptionProvider provider, JLabel error){ + this.provider=provider; + this.error=error; + } + + public int getRowCount() { + return provider.getOptionKeys().length; + } + + public int getColumnCount() { + return 2; + } + + public String getColumnName(int col){ + return TableHeader[col]; + } + + public Object getValueAt(int rowIndex, int columnIndex) { + String key=provider.getOptionKeys()[rowIndex]; + if(columnIndex==0){ + return key; + }else if(columnIndex==1){ + return provider.getOption(key); + } + return null; + } + + public void setValueAt(Object o, int row, int col){ + if(col==0) return; + String key=provider.getOptionKeys()[row]; + Validator v=provider.getOptionValidator(key); + if(v.validate(o)){ + error.setText(""); + provider.setOption(key,o); + } else error.setText(v.getLastErrorMessage()); + } + + public boolean isCellEditable(int row, int col){ + return col!=0; + } + + } + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/OptionProvider.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/OptionProvider.java new file mode 100644 index 000000000000..a45888bf4b66 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/OptionProvider.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.options; + +/** + * Interface for classes that want to publish options. + * + * @author Stefan Loidl + */ +public interface OptionProvider { + + /** + * Returns all keys for existing options. + */ + public String[] getOptionKeys(); + + /** + * Sets the option defined by key. + */ + public boolean setOption(String key, Object value); + + /** + * Gets the option defined by key. + */ + public Object getOption(String key); + + /** + * Returns the class the option should passed as. + */ + public Class getOptionClass(String key); + + /** + * Returns a validator for a option. + */ + public Validator getOptionValidator(String key); + + /** + * Return the description for the option. + */ + public String getOptionDescription(String key); +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/Validator.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/Validator.java new file mode 100644 index 000000000000..eaec293e6446 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/dataflow/options/Validator.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.dataflow.options; + +/** + * A Validator is used to verifiy if an option has the correct format. + * + * @author Stefan Loidl + */ +public interface Validator { + + /** + * Validates if the option has correct format. + */ + public boolean validate(Object option); + + /** + * Returns the Error Message of the last validation or null + * if it was okay. + */ + public String getLastErrorMessage(); +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/BasicLineGenerator.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/BasicLineGenerator.java new file mode 100644 index 000000000000..d46656047208 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/BasicLineGenerator.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import java.awt.Point; +import java.util.List; + +/** + * + * @author Thomas Wuerthinger + */ +public class BasicLineGenerator implements LineGenerator { + public List createLine(List line, Point startRefPoint, Point endRefPoint) { + return line; + } + + public String iconResource() { + return "at/ssw/graphanalyzer/coordinator/images/polygon_lines.gif"; + } + + public String getName() { + return "Polygon lines"; + } +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/BezierLineGenerator.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/BezierLineGenerator.java new file mode 100644 index 000000000000..c6b40ad435d2 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/BezierLineGenerator.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import java.awt.Point; +import java.util.List; + +/** + * + * @author Thomas Wuerthinger + */ +public class BezierLineGenerator implements LineGenerator{ + public List createLine(List lines, Point startRefPoint, Point endRefPoint) { + return Curves.convertToBezier(lines, startRefPoint, endRefPoint, 0.2, 100); + } + + public String iconResource() { + return "at/ssw/graphanalyzer/coordinator/images/bezier_lines.gif"; + } + + public String getName() { + return "Bezier lines"; + } +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterEdge.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterEdge.java new file mode 100644 index 000000000000..a426f961bbed --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterEdge.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Port; +import java.awt.Point; +import java.util.List; + +/** + * + * @author Thomas Wuerthinger + */ +public class ClusterEdge implements Link{ + + private ClusterNode from; + private ClusterNode to; + private List points; + + public ClusterEdge(ClusterNode from, ClusterNode to) { + assert from != null; + assert to != null; + this.from = from; + this.to = to; + } + + public Port getTo() { + return to.getInputSlot(); + } + + public Port getFrom() { + return from.getInputSlot(); + } + + public void setControlPoints(List p) { + this.points = p; + } + + public List getControlPoints() { + return points; + } + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterIngoingConnection.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterIngoingConnection.java new file mode 100644 index 000000000000..60743d9660a9 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterIngoingConnection.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Port; +import java.awt.Point; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Thomas Wuerthinger + */ +public class ClusterIngoingConnection implements Link{ + + private List controlPoints; + private ClusterInputSlotNode inputSlotNode; + private Link connection; + private Port inputSlot; + private Port outputSlot; + + public ClusterIngoingConnection(ClusterInputSlotNode inputSlotNode, Link c) { + this.inputSlotNode = inputSlotNode; + this.connection = c; + this.controlPoints = new ArrayList(); + + inputSlot = c.getTo(); + outputSlot = inputSlotNode.getOutputSlot(); + } + + public Link getConnection() { + return connection; + } + + public ClusterInputSlotNode getInputSlotNode() { + return inputSlotNode; + } + + public Port getTo() { + return inputSlot; + } + + public Port getFrom() { + return outputSlot; + } + + public void setControlPoints(List p) { + this.controlPoints = p; + } + + public List getControlPoints() { + return controlPoints; + } + +} \ No newline at end of file diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterInputSlotNode.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterInputSlotNode.java new file mode 100644 index 000000000000..dd23710c8843 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterInputSlotNode.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import at.ssw.positionmanager.Cluster; +import at.ssw.positionmanager.Port; +import at.ssw.positionmanager.Vertex; +import java.awt.Dimension; +import java.awt.Point; + +/** + * + * @author Thomas Wuerthinger + */ +public class ClusterInputSlotNode implements Vertex { + + private final int SIZE = 0; + private Point position; + private Port inputSlot; + private Port outputSlot; + private ClusterNode blockNode; + private InterClusterConnection interBlockConnection; + private Cluster cluster; + private boolean dirty; + private ClusterIngoingConnection conn; + + public void setIngoingConnection(ClusterIngoingConnection c) { + conn = c; + } + + public ClusterIngoingConnection getIngoingConnection() { + return conn; + } + + private String id; + + public String toString() { + return id; + } + + public ClusterInputSlotNode(ClusterNode n, String id) { + this.blockNode = n; + this.id = id; + + n.addSubNode(this); + + final Vertex thisNode = this; + final ClusterNode thisBlockNode = blockNode; + + outputSlot = new Port() { + + public Point getRelativePosition() { + return new Point(0, 0); + } + + public Vertex getVertex() { + return thisNode; + } + + public String toString() { + return "OutPort of " + thisNode.toString(); + } + }; + + inputSlot = new Port() { + + public Point getRelativePosition() { + Point p = new Point(thisNode.getPosition()); + Point blockPos = thisBlockNode.getPosition(); + p.x -= blockPos.x; + p.y -= blockPos.y; + return p; + } + + public Vertex getVertex() { + return thisBlockNode; + } + public String toString() { + return "InPort of " + thisNode.toString(); + } + }; + } + + public Port getInputSlot() { + return inputSlot; + } + + public InterClusterConnection getInterBlockConnection() { + return interBlockConnection; + } + + public Port getOutputSlot() { + return outputSlot; + } + + + public Dimension getSize() { + return new Dimension(SIZE, SIZE); + } + + public void setPosition(Point p) { + this.position = p; + } + + public Point getPosition() { + return position; + } + + public void setInterBlockConnection(InterClusterConnection interBlockConnection) { + this.interBlockConnection = interBlockConnection; + } + + public Cluster getCluster() { + return cluster; + } + + public boolean isDirty() { + return dirty; + } + + public boolean isRoot() { + return true; + } + + public int compareTo(Vertex o) { + return toString().compareTo(o.toString()); + } + + public boolean isExpanded() { + return false; + } + + public boolean isFixed() { + return false; + } + + public boolean isMarked() { + return false; + } +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterNode.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterNode.java new file mode 100644 index 000000000000..6eecd5cdd76d --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterNode.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import at.ssw.positionmanager.Cluster; +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Port; +import at.ssw.positionmanager.Vertex; +import java.awt.Dimension; +import java.awt.Point; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * + * @author Thomas Wuerthinger + */ +public class ClusterNode implements Vertex { + + private Cluster cluster; + private Port inputSlot; + private Port outputSlot; + private Set subNodes; + private Dimension size; + private Point position; + private Set subEdges; + private boolean dirty; + private boolean root; + private String name; + public static final int BORDER = 10; + + public ClusterNode(Cluster cluster, String name) { + this.subNodes = new HashSet(); + this.subEdges = new HashSet(); + this.cluster = cluster; + position = new Point(0, 0); + this.name = name; + } + + public void addSubNode(Vertex v) { + subNodes.add(v); + } + + public void addSubEdge(Link l) { + subEdges.add(l); + } + + public Set getSubEdges() { + return Collections.unmodifiableSet(subEdges); + } + + public void updateSize() { + + + calculateSize(); + + final ClusterNode widget = this; + inputSlot = new Port() { + + public Point getRelativePosition() { + return new Point(size.width/2, 0); + } + + public Vertex getVertex() { + return widget; + } + }; + + outputSlot = new Port() { + + public Point getRelativePosition() { + return new Point(size.width/2, size.height); + } + + public Vertex getVertex() { + return widget; + } + }; + } + + private void calculateSize() { + + if(subNodes.size() == 0) { + size = new Dimension(0, 0); + } + + int minX = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE; + int minY = Integer.MAX_VALUE; + int maxY = Integer.MIN_VALUE; + + + for(Vertex n : subNodes) { + Point p = n.getPosition(); + minX = Math.min(minX, p.x); + minY = Math.min(minY, p.y); + maxX = Math.max(maxX, p.x + n.getSize().width); + maxY = Math.max(maxY, p.y + n.getSize().height); + } + + size = new Dimension(maxX - minX, maxY - minY); + size.width += 2 * BORDER; + size.height += 2 * BORDER; + + } + + public Port getInputSlot() { + return inputSlot; + + } + + public Port getOutputSlot() { + return outputSlot; + } + + public Dimension getSize() { + return size; + } + + public Point getPosition() { + return position; + } + + + public void setPosition(Point pos) { + this.position = pos; + for(Vertex n : subNodes) { + Point cur = new Point(n.getPosition()); + cur.translate(pos.x, pos.y); + n.setPosition(cur); + } + + for(Link e : subEdges) { + List arr = e.getControlPoints(); + ArrayList newArr = new ArrayList(); + for(Point p : arr) { + Point p2 = new Point(p); + p2.translate(pos.x, pos.y); + newArr.add(p2); + } + + e.setControlPoints(newArr); + } + } + + public Cluster getCluster() { + return cluster; + } + + public void setCluster(Cluster c) { + cluster = c; + } + + public boolean isDirty() { + return dirty; + } + + public void setDirty(boolean b) { + dirty = b; + } + + public void setRoot(boolean b) { + root = b; + } + + public boolean isRoot() { + return root; + } + + public int compareTo(Vertex o) { + return toString().compareTo(o.toString()); + } + + public String toString() { + return name; + } + + public Set getSubNodes() { + return subNodes; + } + + public boolean isExpanded() { + return false; + } + + public boolean isFixed() { + return false; + } + + public boolean isMarked() { + return false; + } + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterOutgoingConnection.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterOutgoingConnection.java new file mode 100644 index 000000000000..8b675bf75e18 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterOutgoingConnection.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Port; +import java.awt.Point; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Thomas Wuerthinger + */ +public class ClusterOutgoingConnection implements Link{ + + private List intermediatePoints; + private ClusterOutputSlotNode outputSlotNode; + private Link connection; + private Port inputSlot; + private Port outputSlot; + + public ClusterOutgoingConnection(ClusterOutputSlotNode outputSlotNode, Link c) { + this.outputSlotNode = outputSlotNode; + this.connection = c; + this.intermediatePoints = new ArrayList(); + + outputSlot = c.getFrom(); + inputSlot = outputSlotNode.getInputSlot(); + } + + public Port getTo() { + return inputSlot; + } + + public Port getFrom() { + return outputSlot; + } + + public void setControlPoints(List p) { + this.intermediatePoints = p; + } + + public List getControlPoints() { + return intermediatePoints; + } + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterOutputSlotNode.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterOutputSlotNode.java new file mode 100644 index 000000000000..166a46a0a0a9 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/ClusterOutputSlotNode.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import at.ssw.positionmanager.Cluster; +import at.ssw.positionmanager.Port; +import at.ssw.positionmanager.Vertex; +import java.awt.Dimension; +import java.awt.Point; + +/** + * + * @author Thomas Wuerthinger + */ +public class ClusterOutputSlotNode implements Vertex{ + + private final int SIZE = 0; + private Point position; + private Port inputSlot; + private Port outputSlot; + private ClusterNode blockNode; + private ClusterOutgoingConnection outgoingConnection; + private boolean dirty; + private boolean root; + private Cluster cluster; + private ClusterOutgoingConnection conn; + private String id; + + public void setOutgoingConnection(ClusterOutgoingConnection c) { + this.conn = c; + } + + public ClusterOutgoingConnection getOutgoingConnection() { + return conn; + } + + public String toString() { + return id; + } + + public ClusterOutputSlotNode(ClusterNode n, String id) { + this.blockNode = n; + this.id = id; + + n.addSubNode(this); + + final Vertex thisNode = this; + final ClusterNode thisBlockNode = blockNode; + + inputSlot = new Port() { + + public Point getRelativePosition() { + return new Point(0, 0); + } + + public Vertex getVertex() { + return thisNode; + } + + public String toString() { + return "InPort of " + thisNode.toString(); + } + + }; + + outputSlot = new Port() { + + public Point getRelativePosition() { + Point p = new Point(thisNode.getPosition()); + Point blockPos = thisBlockNode.getPosition(); + p.x -= blockPos.x; + p.y -= blockPos.y; + return p; + } + + public Vertex getVertex() { + return thisBlockNode; + } + + public String toString() { + return "OutPort of " + thisNode.toString(); + } + + }; + } + + + public Dimension getSize() { + return new Dimension(SIZE, SIZE); + } + + public void setPosition(Point p) { + this.position = p; + } + + public Point getPosition() { + return position; + } + + public Port getInputSlot() { + return inputSlot; + } + + public Port getOutputSlot() { + return outputSlot; + } + + public void setCluster(Cluster c) { + cluster = c; + } + + public void setDirty(boolean b) { + dirty = b; + } + + public void setRoot(boolean b) { + root = b; + } + + public Cluster getCluster() { + return cluster; + } + + public boolean isDirty() { + return dirty; + } + + public boolean isRoot() { + return root; + } + + public int compareTo(Vertex o) { + return toString().compareTo(o.toString()); + } + + public boolean isExpanded() { + return false; + } + + public boolean isFixed() { + return false; + } + + public boolean isMarked() { + return false; + } +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Curves.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Curves.java new file mode 100644 index 000000000000..bcf790b5a256 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Curves.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.RenderingHints; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Thomas Wuerthinger + */ +public class Curves { + + + // Bsplines taken and modified from http://www.cse.unsw.edu.au/~lambert/splines/Bspline.java + // the basis function for a cubic B spline + private static float b(int i, float t) { + switch (i) { + case -2: + return (((-t+3)*t-3)*t+1)/6; + case -1: + return (((3*t-6)*t)*t+4)/6; + case 0: + return (((-3*t+3)*t+3)*t+1)/6; + case 1: + return (t*t*t)/6; + } + return 0; //we only get here if an invalid i is specified + } + + // evaluate a point on the B spline + private static Point p(int i, float t, List points) { + float px=0; + float py=0; + for (int j = -2; j<=1; j++){ + Point point = points.get(i + j); + px += b(j,t)*point.x; + py += b(j,t)*point.y; + } + return new Point(Math.round(px),Math.round(py)); + } + + + public static List bsplines(List inputPoints, Point startRef, Point endRef, int steps) { + if(inputPoints.size() == 2) return inputPoints; + List points = new ArrayList(inputPoints); + Point firstPoint = inputPoints.get(0); + Point lastPoint = inputPoints.get(inputPoints.size() - 1); + + List result = new ArrayList(); + Point q = p(2, 0, points); + for(int i= 2; i < points.size() - 1; i++) { + for(int j=1; j<=steps; j++) { + q = p(i, j/(float)steps, points); + result.add(q); + } + } + result.add(0, firstPoint); + result.add(lastPoint); + return result; + } + + public static Point scaleVector(Point p, double len) { + double scale = Math.sqrt(p.x * p.x + p.y * p.y); + scale = len / scale; + Point result = new Point(p); + result.x = (int)Math.round((double)result.x * scale); + result.y = (int)Math.round((double)result.y * scale); + return result; + } + + public static int quadraticOffset(Point p1, Point p2) { + return (p1.x - p2.x) *(p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y); + } + + public static List convertToBezier(List list, Point startRefPoint, + Point endRefPoint, double bezierScale, int numberOfIntermediatePoints) { + + // Straight line, no bezier needed + if (list.size() == 2) { + return list; + } + + List result = new ArrayList(); + + Point prev = null; + for (int i = 0; i < list.size() - 1; i++) { + Point cur = list.get(i); + Point next = list.get(i + 1); + Point nextnext = null; + if (i < list.size() - 2) { + nextnext = list.get(i + 2); + } + + Point bezierFrom = null; + Point bezierTo = null; + + if (prev == null) { + bezierFrom = new Point(cur.x - startRefPoint.x, cur.y + - startRefPoint.y); + } else { + bezierFrom = new Point(next.x - prev.x, next.y - prev.y); + } + + if (nextnext == null) { + bezierTo = new Point(next.x - endRefPoint.x, next.y + - endRefPoint.y); + } else { + bezierTo = new Point(cur.x - nextnext.x, cur.y - nextnext.y); + } + + Point vec = new Point(cur.x - next.x, cur.y - next.y); + double len = Math.sqrt(vec.x * vec.x + vec.y * vec.y); + double scale = len * bezierScale; + + bezierFrom = scaleVector(bezierFrom, scale); + bezierFrom.translate(cur.x, cur.y); + bezierTo = scaleVector(bezierTo, scale); + bezierTo.translate(next.x, next.y); + + List curList = bezier(cur, next, bezierFrom, bezierTo, + 1 / (double) numberOfIntermediatePoints); + for(Point p : curList) { + if(result.size() > 0) { + Point other = result.get(result.size() - 1); + if(quadraticOffset(p, other) > 80) { + result.add(p); + } + } else { + result.add(p); + } + } + prev = cur; + } + + result.add(list.get(list.size() - 1)); + return result; + } + + private static List bezier(Point from, Point to, Point bezierFrom, + Point bezierTo, double offset) { + double t; + List list = new ArrayList(); + + // from = P0 + // to = P3 + // bezierFrom = P1 + // bezierTo = P2 + + Point lastPoint = new Point(-1, -1); + + for (t = 0.0; t <= 1.0; t += offset) { + double tInv = 1.0 - t; + double tInv2 = tInv * tInv; + double tInv3 = tInv2 * tInv; + double t2 = t * t; + double t3 = t2 * t; + double x = tInv3 * from.x + 3 * t * tInv2 * bezierFrom.x + 3 * t2 + * tInv * bezierTo.x + t3 * to.x; + double y = tInv3 * from.y + 3 * t * tInv2 * bezierFrom.y + 3 * t2 + * tInv * bezierTo.y + t3 * to.y; + Point p = new Point((int)Math.round(x), (int)Math.round(y)); + if (lastPoint.x != p.x || lastPoint.y != p.y) { + int offx = p.x - lastPoint.x; + int offy = p.y - lastPoint.y; + int off = offx * offx + offy * offy; + if (list.size() == 1 || off > 2) { + list.add(p); + lastPoint = p; + } + } + } + + list.remove(list.size() - 1); + list.add(to); + + return list; + } +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Edge.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Edge.java new file mode 100644 index 000000000000..c1291c03310c --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Edge.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +/** + * + * @author Thomas Wuerthinger + */ +public class Edge { + + private E data; + private Node source; + private Node dest; + private Graph graph; + + protected Edge(Graph graph, Node source, Node dest, E data) { + setData(data); + this.graph = graph; + this.source = source; + this.dest = dest; + } + + public Node getSource() { + return source; + } + + public Node getDest() { + return dest; + } + + public E getData() { + return data; + } + + public void setData(E e){ + data = e; + } + + public void remove() { + graph.removeEdge(this, null); + } + + public boolean isSelfLoop() { + return source == dest; + } + + public void reverse() { + + // Remove from current source / dest + source.removeOutEdge(this); + dest.removeInEdge(this); + + Node tmp = source; + source = dest; + dest = tmp; + + // Add to new source / dest + source.addOutEdge(this); + dest.addInEdge(this); + } + + public String toString() { + return "Edge (" + source + " -- " + dest + "): " + data; + } + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Graph.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Graph.java new file mode 100644 index 000000000000..cb4c2309af16 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Graph.java @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * + * @author Thomas Wuerthinger + */ +public class Graph { + + private Hashtable> nodes; + private Hashtable> edges; + private List> nodeList; + private List> edgeList; + + + /** Creates a new instance of Graph */ + public Graph() { + nodes = new Hashtable>(); + edges = new Hashtable>(); + nodeList = new ArrayList>(); + edgeList = new ArrayList>(); + } + + + public Node createNode(N data, Object key) { + Node n = new Node(this, data); + assert key == null || !nodes.containsKey(key); + if(key != null) nodes.put(key, n); + nodeList.add(n); + //assert nodes.containsValue(n); + return n; + } + + public Edge createEdge(Node source, Node dest, E data, Object key) { + //assert nodes.containsValue(source); + //assert nodes.containsValue(dest); + Edge e = new Edge(this, source, dest, data); + source.addOutEdge(e); + dest.addInEdge(e); + if(key != null) edges.put(key, e); + edgeList.add(e); + return e; + } + + public Node getNode(Object key) { + return nodes.get(key); + } + + public Edge getEdge(Object key) { + return edges.get(key); + } + + public Collection> getEdges() { + return Collections.unmodifiableCollection(edges.values()); + } + + public Collection> getNodes() { + return Collections.unmodifiableList(nodeList); + } + + public void removeEdge(Edge e, Object key) { + assert key == null || edges.containsKey(key); + if(key != null) edges.remove(key); + edgeList.remove(e); + e.getSource().removeOutEdge(e); + e.getDest().removeInEdge(e); + } + + public class DFSTraversalVisitor { + public void visitNode(Node n) {} + public boolean visitEdge(Edge e, boolean backEdge){return true;} + } + + public class BFSTraversalVisitor { + public void visitNode(Node n, int depth){} + } + + public List> getNodesWithInDegree(int x) { + return getNodesWithInDegree(x, true); + } + public List> getNodesWithInDegree(int x, boolean countSelfLoops) { + + List> result = new ArrayList>(); + for(Node n : getNodes()) { + if(n.getInDegree(countSelfLoops) == x) { + result.add(n); + } + } + + return result; + + } + + private void markReachable(Node startingNode) { + ArrayList> arr = new ArrayList>(); + arr.add(startingNode); + for(Node n : getNodes()) { + n.setReachable(false); + } + traverseDFS(arr, new DFSTraversalVisitor() { + public void visitNode(Node n) { + n.setReachable(true); + } + + }); + } + + public void traverseBFS(Node startingNode, BFSTraversalVisitor tv, boolean longestPath) { + + //if(longestPath) { + // assert !this.hasCycles(); + //} + + + if(longestPath) { + markReachable(startingNode); + } + + for(Node n : getNodes()) { + n.setVisited(false); + n.setActive(false); + } + + Queue> queue = new LinkedList>(); + queue.add(startingNode); + startingNode.setVisited(true); + int layer = 0; + Node lastOfLayer = startingNode; + Node lastAdded = null; + + while(!queue.isEmpty()) { + + Node current = queue.poll(); + tv.visitNode(current, layer); + current.setActive(false); + + + for(Edge e : current.getOutEdges()) { + if(!e.getDest().isVisited()) { + + boolean allow = true; + if(longestPath) { + for(Node pred : e.getDest().getPredecessors()) { + if((!pred.isVisited() || pred.isActive()) && pred.isReachable()) { + allow = false; + break; + } + } + } + + if(allow) { + queue.offer(e.getDest()); + lastAdded = e.getDest(); + e.getDest().setVisited(true); + e.getDest().setActive(true); + } + } + } + + if(current == lastOfLayer && !queue.isEmpty()) { + lastOfLayer = lastAdded; + layer++; + } + } + } + + public void traverseDFS(DFSTraversalVisitor tv) { + traverseDFS(getNodes(), tv); + } + + public void traverseDFS(Collection> startingNodes, DFSTraversalVisitor tv) { + + for(Node n : getNodes()) { + n.setVisited(false); + n.setActive(false); + } + + boolean result = false; + for(Node n : startingNodes) { + traverse(tv, n); + } + } + + private void traverse(DFSTraversalVisitor tv, Node n) { + + if(!n.isVisited()) { + n.setVisited(true); + n.setActive(true); + tv.visitNode(n); + + for(Edge e : n.getOutEdges()) { + + Node next = e.getDest(); + if(next.isActive()) { + tv.visitEdge(e, true); + } else { + if(tv.visitEdge(e, false)) { + traverse(tv, next); + } + } + } + + n.setActive(false); + } + + } + + public boolean hasCycles() { + + for(Node n : getNodes()) { + n.setVisited(false); + n.setActive(false); + } + + boolean result = false; + for(Node n : getNodes()) { + result |= checkCycles(n); + if(result) break; + } + return result; + } + + private boolean checkCycles(Node n) { + + if(n.isActive()) { + return true; + } + + if(!n.isVisited()) { + + n.setVisited(true); + n.setActive(true); + + for(Node succ : n.getSuccessors()) { + if(checkCycles(succ)) { + return true; + } + } + + n.setActive(false); + + } + + return false; + + } + + public String toString() { + + StringBuilder s = new StringBuilder(); + s.append("Nodes: "); + for(Node n : getNodes()) { + s.append(n.toString()); + s.append("\n"); + } + + + s.append("Edges: "); + + for(Edge e : getEdges()) { + s.append(e.toString()); + s.append("\n"); + } + + return s.toString(); + } + + + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/HierarchicalClusterLayoutManager.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/HierarchicalClusterLayoutManager.java new file mode 100644 index 000000000000..b0394a76f8f4 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/HierarchicalClusterLayoutManager.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import at.ssw.graphanalyzer.positioning.HierarchicalLayoutManager.Combine; +import at.ssw.positionmanager.Cluster; +import at.ssw.positionmanager.LayoutGraph; +import at.ssw.positionmanager.LayoutManager; +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Port; +import at.ssw.positionmanager.Vertex; +import java.awt.Point; +import java.util.Hashtable; +import java.util.List; +import java.util.Set; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.TreeSet; +import at.ssw.graphanalyzer.positioning.HierarchicalLayoutManager; + +/** + * + * @author Thomas Wuerthinger + */ +public class HierarchicalClusterLayoutManager implements LayoutManager{ + + private HierarchicalLayoutManager.Combine combine; + + /** Creates a new instance of HierarchicalClusterLayoutManager */ + public HierarchicalClusterLayoutManager(Combine combine) { + this.combine = combine; + } + + public void doLayout(LayoutGraph graph) { + + assert graph.verify(); + + HierarchicalLayoutManager subManager = new HierarchicalLayoutManager(combine); + HierarchicalLayoutManager manager = new HierarchicalLayoutManager(combine, 150); + Hashtable> lists = new Hashtable>(); + Hashtable> listsConnection = new Hashtable>(); + Set vertices = graph.getVertices(); + Hashtable> clusterInputSlotHash = new Hashtable>(); + Hashtable> clusterOutputSlotHash = new Hashtable>(); + + Hashtable clusterNodes = new Hashtable(); + Hashtable> clusterInputSlotSet = new Hashtable>(); + Hashtable> clusterOutputSlotSet = new Hashtable>(); + Set clusterEdges = new HashSet(); + Set interClusterEdges = new HashSet(); + Hashtable linkClusterOutgoingConnection = new Hashtable(); + Hashtable linkInterClusterConnection = new Hashtable(); + Hashtable linkClusterIngoingConnection = new Hashtable(); + + Set cluster = graph.getClusters(); + int z = 0; + for(Cluster c : cluster) { + lists.put(c, new ArrayList()); + + listsConnection.put(c, new ArrayList()); + clusterInputSlotHash.put(c, new Hashtable()); + clusterOutputSlotHash.put(c, new Hashtable()); + clusterOutputSlotSet.put(c, new TreeSet()); + clusterInputSlotSet.put(c, new TreeSet()); + clusterNodes.put(c, new ClusterNode(c, "" + z)); + z++; + + } + + // Add cluster edges + for(Cluster c : cluster) { + + ClusterNode start = clusterNodes.get(c); + + for(Cluster succ : c.getSuccessors()) { + ClusterNode end = clusterNodes.get(succ); + if(end != null) { + ClusterEdge e = new ClusterEdge(start, end); + clusterEdges.add(e); + interClusterEdges.add(e); + } + } + } + + for(Vertex v : graph.getVertices()) { + + Cluster c = v.getCluster(); + clusterNodes.get(c).addSubNode(v); + + } + + for(Link l : graph.getLinks()) { + + Port fromPort = l.getFrom(); + Port toPort = l.getTo(); + Vertex fromVertex = fromPort.getVertex(); + Vertex toVertex = toPort.getVertex(); + Cluster fromCluster = fromVertex.getCluster(); + Cluster toCluster = toVertex.getCluster(); + + Port samePort = null; + if(combine == Combine.SAME_INPUTS) { + samePort = toPort; + } else if(combine == Combine.SAME_OUTPUTS) { + samePort = fromPort; + } + + assert listsConnection.containsKey(fromCluster); + assert listsConnection.containsKey(toCluster); + + if(fromCluster == toCluster) { + listsConnection.get(fromCluster).add(l); + clusterNodes.get(fromCluster).addSubEdge(l); + } else { + + ClusterInputSlotNode inputSlotNode = null; + ClusterOutputSlotNode outputSlotNode = null; + + if(samePort != null) { + outputSlotNode = clusterOutputSlotHash.get(fromCluster).get(samePort); + inputSlotNode = clusterInputSlotHash.get(toCluster).get(samePort); + } + + boolean needInterClusterConnection = (outputSlotNode == null || inputSlotNode == null); + + if(outputSlotNode == null) { + outputSlotNode = new ClusterOutputSlotNode(clusterNodes.get(fromCluster), "Out " + fromCluster.toString() + " " + samePort.toString()); + clusterOutputSlotSet.get(fromCluster).add(outputSlotNode); + ClusterOutgoingConnection conn = new ClusterOutgoingConnection(outputSlotNode, l); + outputSlotNode.setOutgoingConnection(conn); + clusterNodes.get(fromCluster).addSubEdge(conn); + if(samePort != null) { + clusterOutputSlotHash.get(fromCluster).put(samePort, outputSlotNode); + } + + linkClusterOutgoingConnection.put(l, conn); + } else { + linkClusterOutgoingConnection.put(l, outputSlotNode.getOutgoingConnection()); + } + + if(inputSlotNode == null) { + inputSlotNode = new ClusterInputSlotNode(clusterNodes.get(toCluster), "In " + toCluster.toString() + " " + samePort.toString()); + clusterInputSlotSet.get(toCluster).add(inputSlotNode); + } + + ClusterIngoingConnection conn = new ClusterIngoingConnection(inputSlotNode, l); + inputSlotNode.setIngoingConnection(conn); + clusterNodes.get(toCluster).addSubEdge(conn); + if(samePort != null) { + clusterInputSlotHash.get(toCluster).put(samePort, inputSlotNode); + } + + linkClusterIngoingConnection.put(l, conn); + /*} else { + linkClusterIngoingConnection.put(l, inputSlotNode.getIngoingConnection()); + }*/ + + InterClusterConnection interConn = new InterClusterConnection(outputSlotNode, inputSlotNode); + linkInterClusterConnection.put(l, interConn); + clusterEdges.add(interConn); + } + } + + for(Cluster c : cluster) { + ClusterNode n = clusterNodes.get(c); + subManager.doLayout(new LayoutGraph(n.getSubEdges(), n.getSubNodes()), clusterInputSlotSet.get(c), clusterOutputSlotSet.get(c)); + n.updateSize(); + } + + Set roots = new LayoutGraph(interClusterEdges).findRootVertices(); + for(Vertex v : roots) { + assert v instanceof ClusterNode; + ((ClusterNode)v).setRoot(true); + } + + manager.doLayout(new LayoutGraph(clusterEdges), new HashSet(), new HashSet(), interClusterEdges); + + + for(Link l : graph.getLinks()) { + + if(linkInterClusterConnection.containsKey(l)) { + ClusterOutgoingConnection conn1 = linkClusterOutgoingConnection.get(l); + InterClusterConnection conn2 = linkInterClusterConnection.get(l); + ClusterIngoingConnection conn3 = linkClusterIngoingConnection.get(l); + + assert conn1 != null; + assert conn2 != null; + assert conn3 != null; + + List points = new ArrayList(); + + points.addAll(conn1.getControlPoints()); + points.addAll(conn2.getControlPoints()); + points.addAll(conn3.getControlPoints()); + + l.setControlPoints(points); + } + + } + } + + + public void doRouting(LayoutGraph graph) { + } +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/HierarchicalLayoutManager.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/HierarchicalLayoutManager.java new file mode 100644 index 000000000000..7ee1e5ff7bbe --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/HierarchicalLayoutManager.java @@ -0,0 +1,1124 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import at.ssw.positionmanager.LayoutGraph; +import at.ssw.positionmanager.LayoutManager; +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Port; +import at.ssw.positionmanager.Vertex; +import at.ssw.graphanalyzer.positioning.Edge; +import at.ssw.graphanalyzer.positioning.Graph; +import at.ssw.graphanalyzer.positioning.Graph.BFSTraversalVisitor; +import at.ssw.graphanalyzer.positioning.Node; +import java.awt.Point; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * + * @author Thomas Wuerthinger + */ +public class HierarchicalLayoutManager implements LayoutManager{ + + public static final int DUMMY_WIDTH = 0; + public static final int DUMMY_HEIGHT = 0; + public static final int LAYER_OFFSET = 50; + public static final int OFFSET = 8; + + public static final boolean VERTICAL_LAYOUT = true; + public static final boolean ASSERT = false; + public static final boolean TRACE = false; + + private Combine combine; + + public enum Combine { + NONE, + SAME_INPUTS, + SAME_OUTPUTS + }; + + private class NodeData { + + private Map reversePositions; + private Vertex node; + private Link edge; + private int layer; + private int x; + private int y; + private int width; + + public NodeData(Vertex node) { + reversePositions = new HashMap(); + layer = -1; + this.node = node; + assert node != null; + + if(VERTICAL_LAYOUT) { + width = node.getSize().width; + } else { + width = node.getSize().height; + } + } + + public NodeData(Link edge) { + layer = -1; + this.edge = edge; + assert edge != null; + + if(VERTICAL_LAYOUT) { + width = DUMMY_WIDTH; + } else { + width = DUMMY_HEIGHT; + } + } + + public Vertex getNode() { + return node; + } + + public Link getEdge() { + return edge; + } + + public int getCoordinate() { + return x; + } + + public int getLayerCoordinate() { + return y; + } + + public void setCoordinate(int x) { + this.x = x; + } + + public int getX() { + if(VERTICAL_LAYOUT) { + return x; + } else { + return y; + } + } + + public int getY() { + if(VERTICAL_LAYOUT) { + return y; + } else { + return x; + } + } + + public void setLayerCoordinate(int y) { + this.y = y; + } + + public void setLayer(int x) { + layer = x; + } + + public int getLayer() { + return layer; + } + + public boolean isDummy() { + return edge != null; + } + + public int getWidth() { + return width; + } + + public void addReversedStartEdge(Edge e) { + assert e.getData().isReversed(); + Port port = e.getData().getEdge().getTo(); + int pos = addReversedPort(port); + Point start = e.getData().getRelativeStart(); + e.getData().addStartPoint(start); + int yCoord = node.getSize().height + width - node.getSize().width; + e.getData().addStartPoint(new Point(start.x, yCoord)); + e.getData().addStartPoint(new Point(pos, yCoord)); + e.getData().setRelativeStart(new Point(pos, 0)); + } + + private int addReversedPort(Port p) { + if(reversePositions.containsKey(p)) { + return reversePositions.get(p); + } else { + width += OFFSET; + reversePositions.put(p, width); + return width; + } + } + + public void addReversedEndEdge(Edge e) { + assert e.getData().isReversed(); + int pos = addReversedPort(e.getData().getEdge().getFrom()); + Point end = e.getData().getRelativeEnd(); + e.getData().setRelativeEnd(new Point(pos, node.getSize().height)); + int yCoord = 0 - width + node.getSize().width; + e.getData().addEndPoint(new Point(pos, yCoord)); + e.getData().addEndPoint(new Point(end.x, yCoord)); + e.getData().addEndPoint(end); + } + + public int getHeight() { + if(isDummy()) { + if(VERTICAL_LAYOUT) { + return DUMMY_HEIGHT; + } else { + return DUMMY_WIDTH; + } + + } else { + if(VERTICAL_LAYOUT) { + return node.getSize().height; + } else { + return node.getSize().width; + } + } + } + + public String toString() { + if(isDummy()) { + return edge.toString() + "(layer=" + layer + ")"; + } else { + return node.toString() + "(layer=" + layer + ")"; + } + } + } + + private class EdgeData { + + private Point relativeEnd; + private Point relativeStart; + private List startPoints; + private List endPoints; + private boolean important; + private boolean reversed; + private Link edge; + + public EdgeData(Link edge) { + this(edge, false); + } + + public EdgeData(Link edge, boolean rev) { + this.edge = edge; + reversed = rev; + relativeStart = edge.getFrom().getRelativePosition(); + relativeEnd = edge.getTo().getRelativePosition(); + assert relativeStart.x >= 0 && relativeStart.x <= edge.getFrom().getVertex().getSize().width; + assert relativeStart.y >= 0 && relativeStart.y <= edge.getFrom().getVertex().getSize().height; + assert relativeEnd.x >= 0 && relativeEnd.x <= edge.getTo().getVertex().getSize().width; + assert relativeEnd.y >= 0 && relativeEnd.y <= edge.getTo().getVertex().getSize().height; + startPoints = new ArrayList(); + endPoints = new ArrayList(); + this.important = true; + } + + public boolean isImportant() { + return important; + } + + public void setImportant(boolean b) { + this.important = b; + } + + public List getStartPoints() { + return startPoints; + } + + public List getEndPoints() { + return endPoints; + } + + public List getAbsoluteEndPoints() { + if(endPoints.size() == 0) return endPoints; + + List result = new ArrayList(); + Point point = edge.getTo().getVertex().getPosition(); + for(Point p : endPoints) { + Point p2 = new Point(p.x + point.x, p.y + point.y); + result.add(p2); + } + + return result; + } + + public List getAbsoluteStartPoints() { + if(startPoints.size() == 0) return startPoints; + + List result = new ArrayList(); + Point point = edge.getFrom().getVertex().getPosition(); + for(Point p : startPoints) { + Point p2 = new Point(p.x + point.x, p.y + point.y); + result.add(p2); + } + + return result; + } + + public void addEndPoint(Point p) { + endPoints.add(p); + } + + public void addStartPoint(Point p) { + startPoints.add(p); + } + + public Link getEdge() { + return edge; + } + + public void setRelativeEnd(Point p) { + relativeEnd = p; + } + + public void setRelativeStart(Point p) { + relativeStart = p; + } + + public Point getRelativeEnd() { + return relativeEnd; + } + + public Point getRelativeStart() { + return relativeStart; + } + + public boolean isReversed() { + return reversed; + } + + public void setReversed(boolean b) { + reversed = b; + } + + public String toString() { + return "EdgeData[reversed=" + reversed + "]"; + } + } + + private Graph graph; + private Map> nodeMap; + private int layerOffset; + + /** Creates a new instance of HierarchicalPositionManager */ + public HierarchicalLayoutManager(Combine combine) { + this(combine, LAYER_OFFSET); + } + + public HierarchicalLayoutManager(Combine combine, int layerOffset) { + this.combine = combine; + this.layerOffset = layerOffset; + } + + + public void doRouting(LayoutGraph graph) { + } + + //public void setPositions(PositionedNode rootNode, List nodes, List edges) { + + + public void doLayout(LayoutGraph layoutGraph) { + doLayout(layoutGraph, new HashSet(), new HashSet()); + } + + public void doLayout(LayoutGraph layoutGraph, Set firstLayerHint, Set lastLayerHint) { + doLayout(layoutGraph, firstLayerHint, lastLayerHint, new HashSet()); + } + + public void doLayout(LayoutGraph layoutGraph, Set firstLayerHint, Set lastLayerHint, Set importantLinksHint) { + + if(TRACE) System.out.println("HierarchicalPositionManager.doLayout called"); + + if(layoutGraph.getVertices().size() == 0) return; + + nodeMap = new HashMap>(); + + graph = new Graph(); + + Set> rootNodes = new HashSet>(); + Set startRootVertices = new HashSet(); + + for(Vertex v : layoutGraph.getVertices()) { + if(v.isRoot()) startRootVertices.add(v); + } + Set rootVertices = layoutGraph.findRootVertices(startRootVertices); + + for(Vertex node : layoutGraph.getVertices()) { + + NodeData data = new NodeData(node); + Node n = graph.createNode(data, node); + nodeMap.put(node, n); + + if(rootVertices.contains(node)) { + rootNodes.add(n); + } + } + + Set links = layoutGraph.getLinks(); + Link[] linkArr = new Link[links.size()]; + links.toArray(linkArr); + + List linkList = new ArrayList(); + for(Link l : linkArr) { + linkList.add(l); + } + + Collections.sort(linkList, new Comparator() { + public int compare(Link o1, Link o2) { + int result = o1.getFrom().getVertex().compareTo(o2.getFrom().getVertex()); + if(result == 0) { + return o1.getTo().getVertex().compareTo(o2.getTo().getVertex()); + } else { + return result; + } + } + }); + + for(Link edge : linkList) { + EdgeData data = new EdgeData(edge); + graph.createEdge(graph.getNode(edge.getFrom().getVertex()), graph.getNode(edge.getTo().getVertex()), data, data); + if(importantLinksHint.size() > 0 && !importantLinksHint.contains(edge)) { + data.setImportant(false); + } + } + + + // STEP 1: Remove cycles! + removeCycles(rootNodes); + if(ASSERT) assert checkRemoveCycles(); + + for(Node n : graph.getNodes()) { + List> edges = new ArrayList>(n.getOutEdges()); + Collections.sort(edges, new Comparator>() { + public int compare(Edge o1, Edge o2) { + return o2.getData().getRelativeEnd().x - o1.getData().getRelativeEnd().x; + }}); + + + for(Edge e : edges) { + + if(e.getData().isReversed()) { + e.getSource().getData().addReversedEndEdge(e); + } + } + } + + for(Node n : graph.getNodes()) { + List> edges = new ArrayList>(n.getInEdges()); + Collections.sort(edges, new Comparator>() { + public int compare(Edge o1, Edge o2) { + return o2.getData().getRelativeStart().x - o1.getData().getRelativeStart().x; + }}); + + + for(Edge e : edges) { + if(e.getData().isReversed()) { + e.getDest().getData().addReversedStartEdge(e); + } + } + } + + // STEP 2: Assign layers! + assignLayers(rootNodes, firstLayerHint, lastLayerHint); + if(ASSERT) assert checkAssignLayers(); + + // Put into layer array + int maxLayer = 0; + for(Node n : graph.getNodes()) { + maxLayer = Math.max(maxLayer, n.getData().getLayer()); + } + + + ArrayList> layers[] = new ArrayList[maxLayer+1]; + int layerSizes[] = new int[maxLayer + 1]; + for(int i=0; i>(); + } + + for(Node n : graph.getNodes()) { + int curLayer = n.getData().getLayer(); + layers[curLayer].add(n); + } + + // STEP 3: Insert dummy nodes! + insertDummyNodes(layers); + if(ASSERT) assert checkDummyNodes(); + + // STEP 4: Assign Y coordinates + assignLayerCoordinates(layers, layerSizes); + + // STEP 5: Crossing reduction + crossingReduction(layers); + + // STEP 6: Assign Y coordinates + assignCoordinates(layers); + + // Assign coordinates of nodes to real objects + for(Node n : graph.getNodes()) { + if(!n.getData().isDummy()) { + + Vertex node = n.getData().getNode(); + node.setPosition(new Point(n.getData().getX(), n.getData().getY())); + } + } + + for(Node n : graph.getNodes()) { + if(!n.getData().isDummy()) { + + Vertex node = n.getData().getNode(); + + List> outEdges = n.getOutEdges(); + for(Edge e : outEdges) { + Node succ = e.getDest(); + if(succ.getData().isDummy()) { + //PositionedEdge edge = succ.getData().getEdge(); + List points = new ArrayList(); + assignToRealObjects(layerSizes, succ, points); + } else { + List points = new ArrayList(); + + EdgeData otherEdgeData = e.getData(); + points.addAll(otherEdgeData.getAbsoluteStartPoints()); + Link otherEdge = otherEdgeData.getEdge(); + Point relFrom = new Point(otherEdgeData.getRelativeStart()); + Point from = otherEdge.getFrom().getVertex().getPosition(); + relFrom.move(relFrom.x + from.x, relFrom.y + from.y); + points.add(relFrom); + + Point relTo = new Point(otherEdgeData.getRelativeEnd()); + Point to = otherEdge.getTo().getVertex().getPosition(); + relTo.move(relTo.x + to.x, relTo.y + to.y); + assert from != null; + assert to != null; + points.add(relTo); + points.addAll(otherEdgeData.getAbsoluteEndPoints()); + e.getData().getEdge().setControlPoints(points); + } + } + } + } + + + } + + public boolean onOneLine(Point p1, Point p2, Point p3) { + int xoff1 = p1.x - p2.x; + int yoff1 = p1.y - p2.y; + int xoff2 = p3.x - p2.x; + int yoff2 = p3.y - p2.x; + + return (xoff1 * yoff2 - yoff1 * xoff2 == 0); + } + + public void assignToRealObjects(int layerSizes[], Node cur, List points) { + assert cur.getData().isDummy(); + + ArrayList otherPoints = new ArrayList(points); + + int size = layerSizes[cur.getData().getLayer()]; + otherPoints.add(new Point(cur.getData().getX(), cur.getData().getY() - size/2)); + if(otherPoints.size() >= 3 && onOneLine(otherPoints.get(otherPoints.size() - 1), otherPoints.get(otherPoints.size() - 2), otherPoints.get(otherPoints.size() - 3))) { + otherPoints.remove(otherPoints.size() - 2); + } + otherPoints.add(new Point(cur.getData().getX(), cur.getData().getY() + size/2)); + if(otherPoints.size() >= 3 && onOneLine(otherPoints.get(otherPoints.size() - 1), otherPoints.get(otherPoints.size() - 2), otherPoints.get(otherPoints.size() - 3))) { + otherPoints.remove(otherPoints.size() - 2); + } + + for(int i=0; i otherSucc = cur.getOutEdges().get(i).getDest(); + + if(otherSucc.getData().isDummy()) { + assignToRealObjects(layerSizes, otherSucc, otherPoints); + } else { + EdgeData otherEdgeData = cur.getOutEdges().get(i).getData(); + Link otherEdge = otherEdgeData.getEdge(); + + List middlePoints = new ArrayList(otherPoints); + if(cur.getOutEdges().get(i).getData().isReversed()) { + Collections.reverse(middlePoints); + } + + ArrayList copy = new ArrayList(); + Point relFrom = new Point(otherEdgeData.getRelativeStart()); + Point from = otherEdge.getFrom().getVertex().getPosition(); + //int moveUp = (size - otherEdge.getFrom().getVertex().getSize().height) / 2; + relFrom.move(relFrom.x + from.x, relFrom.y + from.y); + copy.addAll(otherEdgeData.getAbsoluteStartPoints()); + copy.add(relFrom); + copy.addAll(middlePoints); + + Point relTo = new Point(otherEdgeData.getRelativeEnd()); + Point to = otherEdge.getTo().getVertex().getPosition(); + relTo.move(relTo.x + to.x, relTo.y + to.y); + copy.add(relTo); + + copy.addAll(otherEdgeData.getAbsoluteEndPoints()); + + + otherEdge.setControlPoints(copy); + } + } + } + + + private boolean checkDummyNodes() { + for(Edge e : graph.getEdges()) { + if(e.getSource().getData().getLayer() != e.getDest().getData().getLayer() - 1) { + return false; + } + } + + return true; + } + + private void insertDummyNodes(ArrayList> layers[]) { + + int sum = 0; + List> nodes = new ArrayList>(graph.getNodes()); + int edgeCount = 0; + int innerMostLoop = 0; + + for(Node n : nodes) { + List> edges = new ArrayList>(n.getOutEdges()); + for(Edge e : edges) { + + edgeCount++; + Link edge = e.getData().getEdge(); + Node destNode = e.getDest(); + Node lastNode = n; + Edge lastEdge = e; + + boolean searchForNode = (combine != Combine.NONE); + for(int i=n.getData().getLayer()+1; i foundNode = null; + if(searchForNode) { + for(Node sameLayerNode : layers[i]) { + innerMostLoop++; + + if(combine == Combine.SAME_OUTPUTS) { + if(sameLayerNode.getData().isDummy() && sameLayerNode.getData().getEdge().getFrom() == edge.getFrom()) { + foundNode = sameLayerNode; + break; + } + } else if(combine == Combine.SAME_INPUTS) { + if(sameLayerNode.getData().isDummy() && sameLayerNode.getData().getEdge().getTo() == edge.getTo()) { + foundNode = sameLayerNode; + break; + } + } + } + } + + if(foundNode == null) { + searchForNode = false; + NodeData intermediateData = new NodeData(edge); + Node curNode = graph.createNode(intermediateData, null); + curNode.getData().setLayer(i); + layers[i].add(0, curNode); + sum++; + lastEdge.remove(); + graph.createEdge(lastNode, curNode, e.getData(), null); + assert lastNode.getData().getLayer() == curNode.getData().getLayer() - 1; + lastEdge = graph.createEdge(curNode, destNode, e.getData(), null); + lastNode = curNode; + } else { + lastEdge.remove(); + lastEdge = graph.createEdge(foundNode, destNode, e.getData(), null); + lastNode = foundNode; + } + + } + } + } + + if(TRACE) System.out.println("Number of edges: " + edgeCount); + if(TRACE) System.out.println("Dummy nodes inserted: " + sum); + } + + private void assignLayerCoordinates(ArrayList> layers[], int layerSizes[]) { + int cur = 0; + for(int i=0; i n : layers[i]) { + maxHeight = Math.max(maxHeight, n.getData().getHeight()); + } + + layerSizes[i] = maxHeight; + for(Node n : layers[i]) { + int curCoordinate = cur + (maxHeight - n.getData().getHeight())/2; + n.getData().setLayerCoordinate(curCoordinate); + } + cur += maxHeight + layerOffset; + + } + } + + private void assignCoordinates(ArrayList> layers[]) { + + // TODO: change this + for(int i=0; i> curArray = layers[i]; + int curY = 0; + for(Node n : curArray) { + + n.getData().setCoordinate(curY); + if(!n.getData().isDummy()) { + curY += n.getData().getWidth(); + } + curY += OFFSET; + + } + } + + int curSol = evaluateSolution(); + if(TRACE) System.out.println("First coordinate solution found: " + curSol); + + for(int i=0; i<2; i++) { + optimizeMedian(layers); + curSol = evaluateSolution(); + if(TRACE) System.out.println("Current coordinate solution found: " + curSol); + } + normalizeCoordinate(); + + } + + private void normalizeCoordinate() { + + int min = Integer.MAX_VALUE; + for(Node n : graph.getNodes()) { + min = Math.min(min, n.getData().getCoordinate()); + } + + for(Node n : graph.getNodes()) { + n.getData().setCoordinate(n.getData().getCoordinate() - min); + } + + } + + private void optimizeMedian(ArrayList> layers[]) { + + // Downsweep + for(int i=1; i> processingList = new ArrayList>(layers[i]); + Collections.sort(processingList, new Comparator>() { + public int compare(Node o1, Node o2) { + if(o2.getData().isDummy()) { + return 1; + } else if(o1.getData().isDummy()) { + return -1; + } + return o2.getInEdges().size() - o1.getInEdges().size(); + } + }); + + + ArrayList> alreadyAssigned = new ArrayList>(); + for(Node n : processingList) { + + + ArrayList> preds = new ArrayList>(n.getPredecessors()); + int pos = n.getData().getCoordinate(); + if(preds.size() > 0) { + + Collections.sort(preds, new Comparator>() { + public int compare(Node o1, Node o2) { + return o1.getData().getCoordinate() - o2.getData().getCoordinate(); + } + }); + + if(preds.size() % 2 == 0) { + assert preds.size() >= 2; + pos = (preds.get(preds.size() / 2).getData().getCoordinate() - calcRelativeCoordinate(preds.get(preds.size() / 2), n) + preds.get(preds.size() / 2-1).getData().getCoordinate() - calcRelativeCoordinate(preds.get(preds.size()/2-1), n))/2; + } else { + assert preds.size() >= 1; + pos = preds.get(preds.size() / 2).getData().getCoordinate() - calcRelativeCoordinate(preds.get(preds.size() / 2), n); + } + } + + tryAdding(alreadyAssigned, n, pos); + } + } + // Upsweep + for(int i=layers.length - 2; i >= 0; i--) { + ArrayList> processingList = new ArrayList>(layers[i]); + Collections.sort(processingList, new Comparator>() { + public int compare(Node o1, Node o2) { + if(o2.getData().isDummy()) { + return 1; + } else if(o1.getData().isDummy()) { + return -1; + } + return o2.getOutEdges().size() - o1.getOutEdges().size(); + } + }); + + ArrayList> alreadyAssigned = new ArrayList>(); + for(Node n : processingList) { + + ArrayList> succs = new ArrayList>(n.getSuccessors()); + int pos = n.getData().getCoordinate(); + if(succs.size() > 0) { + + Collections.sort(succs, new Comparator>() { + public int compare(Node o1, Node o2) { + return o1.getData().getCoordinate() - o2.getData().getCoordinate(); + } + }); + + if(succs.size() % 2 == 0) { + assert succs.size() >= 2; + pos = (succs.get(succs.size() / 2).getData().getCoordinate() - calcRelativeCoordinate(n, succs.get(succs.size() / 2)) + succs.get(succs.size()/2-1).getData().getCoordinate() - calcRelativeCoordinate(n, succs.get(succs.size()/2-1)))/2; + } else { + assert succs.size() >= 1; + pos = succs.get(succs.size() / 2).getData().getCoordinate() - calcRelativeCoordinate(n, succs.get(succs.size() / 2)); + } + } + + tryAdding(alreadyAssigned, n, pos); + } + } + } + + private int median(ArrayList arr) { + assert arr.size() > 0; + Collections.sort(arr); + if(arr.size() % 2 == 0) { + return (arr.get(arr.size() / 2) + arr.get(arr.size() / 2 - 1)) / 2; + } else { + return arr.get(arr.size() / 2); + } + } + + private int calcRelativeCoordinate(Node n, Node succ) { + + if(n.getData().isDummy() && succ.getData().isDummy()) return 0; + + int pos = 0; + int pos2 = 0; + ArrayList coords2 = new ArrayList(); + ArrayList coords = new ArrayList(); + /*if(!n.getData().isDummy())*/ { + for(Edge e : n.getOutEdges()) { + + //System.out.println("reversed: " + e.getData().isReversed()); + if(e.getDest() == succ) { + + if(e.getData().isReversed()) { + if(!n.getData().isDummy()) { + coords.add(e.getData().getRelativeEnd().x); + } + + if(!succ.getData().isDummy()) { + coords2.add(e.getData().getRelativeStart().x); + } + } else { + if(!n.getData().isDummy()) { + coords.add(e.getData().getRelativeStart().x); + } + + if(!succ.getData().isDummy()) { + coords2.add(e.getData().getRelativeEnd().x); + } + } + } + } + + // assert coords.size() > 0; + if(!n.getData().isDummy()) { + pos = median(coords); + } + + if(!succ.getData().isDummy()) { + pos2 = median(coords2); + } + } + //System.out.println("coords=" + coords); + //System.out.println("coords2=" + coords2); + + return pos - pos2; + } + + + private boolean intersect(int v1, int w1, int v2, int w2) { + if(v1 >= v2 && v1 < v2 + w2) return true; + if(v1 + w1 > v2 && v1 + w1 < v2 + w2) return true; + if(v1 < v2 && v1 + w1 > v2) return true; + return false; + } + + private boolean intersect(Node n1, Node n2) { + return intersect(n1.getData().getCoordinate(), n1.getData().getWidth() + OFFSET, n2.getData().getCoordinate(), n2.getData().getWidth() + OFFSET); + } + + private void tryAdding(List> alreadyAssigned, Node node, int pos) { + + boolean doesIntersect = false; + node.getData().setCoordinate(pos); + for(Node n : alreadyAssigned) { + if(intersect(node, n)) { + doesIntersect = true; + break; + } + } + + if(!doesIntersect) { + + // Everything find, just place the node + int insertPosition = 0; + int z = 0; + for(Node n : alreadyAssigned) { + if(pos > n.getData().getCoordinate()) { + insertPosition = z+1; + } else { + break; + } + z++; + } + + if(ASSERT) assert !findOverlap(alreadyAssigned, node); + alreadyAssigned.add(insertPosition, node); + + } else { + + assert alreadyAssigned.size() > 0; + + // Search for alternative location + int minOffset = Integer.MAX_VALUE; + int minIndex = -1; + int minPos = 0; + int w = node.getData().getWidth() + OFFSET; + + // Try bottom-most + minIndex = 0; + minPos = alreadyAssigned.get(0).getData().getCoordinate() - w; + minOffset = Math.abs(minPos - pos); + + // Try top-most + Node lastNode = alreadyAssigned.get(alreadyAssigned.size() - 1); + int lastPos = lastNode.getData().getCoordinate() + lastNode.getData().getWidth() + OFFSET; + int lastOffset = Math.abs(lastPos - pos); + if(lastOffset < minOffset) { + minPos = lastPos; + minOffset = lastOffset; + minIndex = alreadyAssigned.size(); + } + + // Try between + for(int i=0; i curNode = alreadyAssigned.get(i); + Node nextNode = alreadyAssigned.get(i+1); + + int start = curNode.getData().getCoordinate() + curNode.getData().getWidth() + OFFSET; + int end = nextNode.getData().getCoordinate() - OFFSET; + + if(end - start >= node.getData().getWidth()) { + // Node could fit here + int cand1 = start; + int cand2 = end - node.getData().getWidth(); + int off1 = Math.abs(cand1 - pos); + int off2 = Math.abs(cand2 - pos); + if(off1 < minOffset) { + minPos = cand1; + minOffset = off1; + minIndex = i+1; + } + + if(off2 < minOffset) { + minPos = cand2; + minOffset = off2; + minIndex = i+1; + } + } + } + + assert minIndex != -1; + node.getData().setCoordinate(minPos); + if(ASSERT) assert !findOverlap(alreadyAssigned, node); + alreadyAssigned.add(minIndex, node); + } + + } + + private boolean findOverlap(List> nodes, Node node) { + + for(Node n1 : nodes) { + if(intersect(n1, node)) { + return true; + } + } + + return false; + } + + private int evaluateSolution() { + + int sum = 0; + for(Edge e : graph.getEdges()) { + Node source = e.getSource(); + Node dest = e.getDest(); + int offset = 0; + offset = Math.abs(source.getData().getCoordinate() - dest.getData().getCoordinate()); + sum += offset; + } + + return sum; + } + + private void crossingReduction(ArrayList> layers[]) { + + for(int i=0; i> curNodes = layers[i]; + ArrayList> nextNodes = layers[i+1]; + for(Node n : curNodes) { + for(Node succ : n.getSuccessors()) { + if(ASSERT) assert nextNodes.contains(succ); + nextNodes.remove(succ); + nextNodes.add(succ); + } + } + + } + + } + + + + private void removeCycles(Set> rootNodes) { + final List> reversedEdges = new ArrayList>(); + + + int removedCount = 0; + int reversedCount = 0; + + Graph.DFSTraversalVisitor visitor = graph.new DFSTraversalVisitor() { + public boolean visitEdge(Edge e, boolean backEdge) { + if(backEdge) { + if(ASSERT) assert !reversedEdges.contains(e); + reversedEdges.add(e); + //System.out.println("Back edge: " + e); + e.getData().setReversed(!e.getData().isReversed()); + } + + return e.getData().isImportant(); + } + }; + Set> nodes = new HashSet>(); + nodes.addAll(rootNodes); + + assert nodes.size() > 0; + + this.graph.traverseDFS(nodes, visitor); + + for(Edge e : reversedEdges) { + if(e.isSelfLoop()) { + e.remove(); + removedCount++; + } else { + e.reverse(); + reversedCount++; + } + } + } + + private boolean checkRemoveCycles() { + return !graph.hasCycles(); + } + + // Only used by assignLayers + private int maxLayer; + + private void assignLayers(Set> rootNodes, Set firstLayerHints, Set lastLayerHints) { + this.maxLayer = -1; + for(Node n : graph.getNodes()) { + n.getData().setLayer(-1); + } + + Graph.BFSTraversalVisitor traverser = graph.new BFSTraversalVisitor() { + public void visitNode(Node n, int depth) { + if(depth > n.getData().getLayer()) { + n.getData().setLayer(depth); + maxLayer = Math.max(maxLayer, depth); + } + } + }; + + for(Node n : rootNodes) { + if(n.getData().getLayer() == -1) { + this.graph.traverseBFS(n, traverser, true); + } + } + + for(Vertex v : firstLayerHints) { + assert nodeMap.containsKey(v); + nodeMap.get(v).getData().setLayer(0); + } + + for(Vertex v : lastLayerHints) { + assert nodeMap.containsKey(v); + nodeMap.get(v).getData().setLayer(maxLayer); + } + } + + private boolean checkAssignLayers() { + + for(Edge e : graph.getEdges()) { + Node source = e.getSource(); + Node dest = e.getDest(); + + + if(source.getData().getLayer() >= dest.getData().getLayer()) { + return false; + } + } + int maxLayer = 0; + for(Node n : graph.getNodes()) { + assert n.getData().getLayer() >= 0; + if(n.getData().getLayer() > maxLayer) { + maxLayer = n.getData().getLayer(); + } + } + + int countPerLayer[] = new int[maxLayer+1]; + for(Node n : graph.getNodes()) { + countPerLayer[n.getData().getLayer()]++; + } + + if(TRACE) System.out.println("Number of layers: " + maxLayer); + return true; + } + + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/InterClusterConnection.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/InterClusterConnection.java new file mode 100644 index 000000000000..45b58e76a500 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/InterClusterConnection.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import at.ssw.positionmanager.Link; +import at.ssw.positionmanager.Port; +import java.awt.Point; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Thomas Wuerthinger + */ +public class InterClusterConnection implements Link{ + + private Port inputSlot; + private Port outputSlot; + private List intermediatePoints; + private ClusterInputSlotNode inputSlotNode; + private ClusterOutputSlotNode outputSlotNode; + + public InterClusterConnection(ClusterOutputSlotNode outputSlotNode, ClusterInputSlotNode inputSlotNode) { + this.outputSlotNode = outputSlotNode; + this.inputSlotNode = inputSlotNode; + this.inputSlot = inputSlotNode.getInputSlot(); + this.outputSlot = outputSlotNode.getOutputSlot(); + intermediatePoints = new ArrayList(); + } + + public ClusterOutputSlotNode getOutputSlotNode() { + return outputSlotNode; + } + + public Port getTo() { + return inputSlot; + } + + public Port getFrom() { + return outputSlot; + } + + public void setControlPoints(List p) { + this.intermediatePoints = p; + } + + public List getControlPoints() { + return intermediatePoints; + } + + public String toString() { + return "InterClusterConnection[from=" + getFrom() + ", to=" + getTo() + "]"; + } +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/LineGenerator.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/LineGenerator.java new file mode 100644 index 000000000000..6f9971d4cccb --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/LineGenerator.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import java.awt.Point; +import java.util.List; + +/** + * + * @author Thomas Wuerthinger + */ +public interface LineGenerator { + + public List createLine(List line, Point startRefPoint, Point endRefPoint); + public String iconResource(); + public String getName(); + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Node.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Node.java new file mode 100644 index 000000000000..6b4c7b78148b --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/Node.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * + * @author Thomas Wuerthinger + */ +public class Node { + + private N data; + private List> inEdges; + private List> outEdges; + private boolean visited; + private boolean active; + private boolean reachable; + private Graph graph; + + protected boolean isVisited() { + return visited; + } + + protected void setVisited(boolean b) { + visited = b; + } + + protected boolean isReachable() { + return reachable; + } + + protected void setReachable(boolean b) { + reachable = b; + } + + protected boolean isActive() { + return active; + } + + protected void setActive(boolean b) { + active = b; + } + + public int getInDegree() { + return getInDegree(true); + } + + public int getInDegree(boolean countSelfLoops) { + if(countSelfLoops) { + return inEdges.size(); + } else { + int cnt = 0; + for(Edge e : inEdges) { + if(e.getSource() != this) { + cnt++; + } + } + return cnt; + } + } + + public int getOutDegree() { + return outEdges.size(); + } + + protected Node(Graph graph, N data) { + setData(data); + this.graph = graph; + inEdges = new ArrayList>(); + outEdges = new ArrayList>(); + } + + protected void addInEdge(Edge e) { + assert !inEdges.contains(e); + inEdges.add(e); + } + + protected void addOutEdge(Edge e) { + assert !outEdges.contains(e); + outEdges.add(e); + } + + protected void removeInEdge(Edge e) { + assert inEdges.contains(e); + inEdges.remove(e); + } + + protected void removeOutEdge(Edge e) { + assert outEdges.contains(e); + outEdges.remove(e); + } + + public List> getInEdges() { + return Collections.unmodifiableList(inEdges); + } + + public List> getOutEdges() { + return Collections.unmodifiableList(outEdges); + } + + public List> getSuccessors() { + ArrayList> succ = new ArrayList>(); + for(Edge e : getOutEdges()) { + Node n = e.getDest(); + if(!succ.contains(n)) { + succ.add(n); + } + } + return succ; + } + + public List> getPredecessors() { + ArrayList> pred = new ArrayList>(); + for(Edge e : getInEdges()) { + Node n = e.getSource(); + if(!pred.contains(n)) { + pred.add(n); + } + } + return pred; + } + + public N getData() { + return data; + } + + public void setData(N d) { + data = d; + } + + public String toString() { + return "Node: " + data; + } + +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/SplineLineGenerator.java b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/SplineLineGenerator.java new file mode 100644 index 000000000000..a1005dd0e40e --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/java/at/ssw/graphanalyzer/positioning/SplineLineGenerator.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.graphanalyzer.positioning; + +import java.awt.Point; +import java.util.List; + +/** + * + * @author Thomas Wuerthinger + */ +public class SplineLineGenerator implements LineGenerator{ + + public SplineLineGenerator() { + } + + public List createLine(List line, Point startRefPoint, Point endRefPoint) { + return Curves.bsplines(line, startRefPoint, endRefPoint, 30); + } + + public String iconResource() { + return "at/ssw/graphanalyzer/coordinator/images/spline_lines.gif"; + } + + public String getName() { + return "Splines"; + } +} diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..842e6ac1b39d --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/nbm/manifest.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.dataflow +OpenIDE-Module-Localizing-Bundle: at/ssw/dataflow/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/src/main/resources/at/ssw/dataflow/Bundle.properties b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/resources/at/ssw/dataflow/Bundle.properties new file mode 100644 index 000000000000..46629a573cb9 --- /dev/null +++ b/visualizer/C1Visualizer/GraphLayoutImpl/src/main/resources/at/ssw/dataflow/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Graph Layout Impl diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/pom.xml b/visualizer/C1Visualizer/IntermediateCodeEditor/pom.xml new file mode 100644 index 000000000000..57dd0669a3b3 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/pom.xml @@ -0,0 +1,123 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + IntermediateCodeEditor + 1.13-SNAPSHOT + nbm + IntermediateCodeEditor + + UTF-8 + + + + at.ssw.visualizer + VisualizerUI + ${project.version} + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + at.ssw.visualizer + TextEditor + ${project.version} + + + org.netbeans.api + org-netbeans-modules-editor + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-fold + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-lib + ${netbeans.version} + + + org.netbeans.api + org-openide-awt + ${netbeans.version} + + + org.netbeans.api + org-openide-nodes + ${netbeans.version} + + + org.netbeans.api + org-openide-text + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-util-ui + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.ir + at.ssw.visualizer.ir.model + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/IREditor.java b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/IREditor.java new file mode 100644 index 000000000000..b2948599596d --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/IREditor.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir; + +import at.ssw.visualizer.texteditor.Editor; +import org.openide.windows.CloneableTopComponent; + +/** + * The actual editor component for displaying IR text. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + * @author Alexander Reder + */ +public class IREditor extends Editor { + + public IREditor(IREditorSupport support) { + super(support); + } + + @Override + protected CloneableTopComponent createClonedObject() { + IREditor editor = new IREditor((IREditorSupport) cloneableEditorSupport()); + editor.setActivatedNodes(getActivatedNodes()); + return editor; + } +} diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/IREditorKit.java b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/IREditorKit.java new file mode 100644 index 000000000000..536e32d03962 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/IREditorKit.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir; + +import at.ssw.visualizer.ir.model.IRScanner; +import at.ssw.visualizer.texteditor.EditorKit; +import java.util.Collection; +import javax.swing.Action; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import javax.swing.text.TextAction; +import org.netbeans.editor.Syntax; +import org.netbeans.modules.editor.NbEditorKit; +import org.openide.util.Lookup; +import org.openide.util.actions.CallableSystemAction; +import org.openide.util.lookup.Lookups; + +/** + * The IR Editor Kit, providing the syntax support. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + */ +public class IREditorKit extends EditorKit { + + @Override + public String getContentType() { + return IREditorSupport.MIME_TYPE; + } + + @Override + public Syntax createSyntax(Document doc) { + return new IRScanner(); + } + + @Override + protected Action[] getCustomActions() { + return TextAction.augmentList(super.getCustomActions(), new Action[]{ + new GenerateFoldPopupAction() + }); + } + + public static class GenerateFoldPopupAction extends NbEditorKit.GenerateFoldPopupAction { + + @Override + public JMenuItem getPopupMenuItem(JTextComponent target) { + JMenuItem menu = super.getPopupMenuItem(target); + menu.add(new JPopupMenu.Separator()); + Lookup lookup = Lookups.forPath("IREditorFolding"); + Collection foldingActions = lookup.lookupAll(CallableSystemAction.class); + for (CallableSystemAction csa : foldingActions) { + menu.add(csa.getMenuPresenter()); + } + return menu; + } + } +} diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/IREditorSupport.java b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/IREditorSupport.java new file mode 100644 index 000000000000..a7a8dd398e18 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/IREditorSupport.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir; + +import at.ssw.visualizer.ir.icons.Icons; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.ir.model.IRTextBuilder; +import at.ssw.visualizer.texteditor.EditorSupport; +import org.openide.text.CloneableEditor; +import org.openide.util.ImageUtilities; + +/** + * Connects a ControlFlowGraph to a NetBeans text editor. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + * @author Alexander Reder + */ +public class IREditorSupport extends EditorSupport { + + public static final String MIME_TYPE = "text/x-compilation-ir"; + + public IREditorSupport(ControlFlowGraph cfg) { + super(cfg); + this.text = new IRTextBuilder().buildDocument(cfg); + } + + @Override + public String getMimeType() { + return MIME_TYPE; + } + + @Override + protected CloneableEditor createCloneableEditor() { + return new IREditor(this); + } + + + @Override + protected void initializeCloneableEditor(CloneableEditor editor) { + super.initializeCloneableEditor(editor); + editor.setIcon(ImageUtilities.loadImage(Icons.IR)); + } + +} diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/CollapseAllAction.java b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/CollapseAllAction.java new file mode 100644 index 000000000000..b18bc44c23b1 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/CollapseAllAction.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.action; + +import at.ssw.visualizer.ir.icons.Icons; +import javax.swing.text.JTextComponent; +import org.netbeans.api.editor.fold.FoldHierarchy; +import org.netbeans.api.editor.fold.FoldUtilities; +import org.netbeans.editor.Utilities; +import org.openide.util.HelpCtx; +import org.openide.util.actions.CallableSystemAction; + +public final class CollapseAllAction extends CallableSystemAction { + public void performAction() { + JTextComponent comp = Utilities.getFocusedComponent(); + FoldHierarchy hierarchy = FoldHierarchy.get(comp); + FoldUtilities.collapseAll(hierarchy); + } + + public String getName() { + return "Collapse All"; + } + + @Override + protected String iconResource() { + return Icons.COLLAPSE_ALL; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + protected boolean asynchronous() { + return false; + } + +} diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ExpandAllAction.java b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ExpandAllAction.java new file mode 100644 index 000000000000..eb5514564455 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ExpandAllAction.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.action; + +import at.ssw.visualizer.ir.icons.Icons; +import javax.swing.text.JTextComponent; +import org.netbeans.api.editor.fold.FoldHierarchy; +import org.netbeans.api.editor.fold.FoldUtilities; +import org.netbeans.editor.Utilities; +import org.openide.util.HelpCtx; +import org.openide.util.actions.CallableSystemAction; + +public final class ExpandAllAction extends CallableSystemAction { + public void performAction() { + JTextComponent comp = Utilities.getFocusedComponent(); + FoldHierarchy hierarchy = FoldHierarchy.get(comp); + FoldUtilities.expandAll(hierarchy); + } + + public String getName() { + return "Expand All"; + } + + @Override + protected String iconResource() { + return Icons.EXPAND_ALL; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + protected boolean asynchronous() { + return false; + } + +} diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ExpandHIRAction.java b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ExpandHIRAction.java new file mode 100644 index 000000000000..129451505d30 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ExpandHIRAction.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.action; + +import at.ssw.visualizer.ir.icons.Icons; +import at.ssw.visualizer.ir.model.IRTextBuilder; +import javax.swing.text.JTextComponent; +import org.netbeans.api.editor.fold.FoldHierarchy; +import org.netbeans.api.editor.fold.FoldUtilities; +import org.netbeans.editor.Utilities; +import org.openide.util.HelpCtx; +import org.openide.util.actions.CallableSystemAction; + +public final class ExpandHIRAction extends CallableSystemAction { + public void performAction() { + JTextComponent comp = Utilities.getFocusedComponent(); + FoldHierarchy hierarchy = FoldHierarchy.get(comp); + FoldUtilities.collapseAll(hierarchy); + FoldUtilities.expand(hierarchy, IRTextBuilder.KIND_HIR); + FoldUtilities.expand(hierarchy, IRTextBuilder.KIND_STATE_WITH_PHIS); + FoldUtilities.expand(hierarchy, IRTextBuilder.KIND_BLOCK); + } + + public String getName() { + return "Expand HIR"; + } + + @Override + protected String iconResource() { + return Icons.EXPAND_HIR; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + protected boolean asynchronous() { + return false; + } + +} diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ExpandLIRAction.java b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ExpandLIRAction.java new file mode 100644 index 000000000000..35f1d1e54f5f --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ExpandLIRAction.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.action; + +import at.ssw.visualizer.ir.icons.Icons; +import at.ssw.visualizer.ir.model.IRTextBuilder; +import javax.swing.text.JTextComponent; +import org.netbeans.api.editor.fold.FoldHierarchy; +import org.netbeans.api.editor.fold.FoldUtilities; +import org.netbeans.editor.Utilities; +import org.openide.util.HelpCtx; +import org.openide.util.actions.CallableSystemAction; + +public final class ExpandLIRAction extends CallableSystemAction { + public void performAction() { + JTextComponent comp = Utilities.getFocusedComponent(); + FoldHierarchy hierarchy = FoldHierarchy.get(comp); + FoldUtilities.collapseAll(hierarchy); + FoldUtilities.expand(hierarchy, IRTextBuilder.KIND_LIR); + FoldUtilities.expand(hierarchy, IRTextBuilder.KIND_BLOCK); + } + + public String getName() { + return "Expand LIR"; + } + + @Override + protected String iconResource() { + return Icons.EXPAND_LIR; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + protected boolean asynchronous() { + return false; + } + +} diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ShowIREditorAction.java b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ShowIREditorAction.java new file mode 100644 index 000000000000..97761ca9f861 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/action/ShowIREditorAction.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.action; + +import at.ssw.visualizer.core.focus.Focus; +import at.ssw.visualizer.ir.IREditor; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.ir.IREditorSupport; +import at.ssw.visualizer.ir.icons.Icons; +import org.openide.nodes.Node; +import org.openide.util.HelpCtx; +import org.openide.util.actions.CookieAction; + +/** + * Opens the IR editor. + * + * @author Bernhard Stiftner + */ +public final class ShowIREditorAction extends CookieAction { + protected void performAction(Node[] activatedNodes) { + ControlFlowGraph cfg = activatedNodes[0].getLookup().lookup(ControlFlowGraph.class); + if (!Focus.findEditor(IREditor.class, cfg)) { + IREditorSupport editor = new IREditorSupport(cfg); + editor.open(); + } + } + + @Override + protected boolean enable(Node[] activatedNodes) { + if (!super.enable(activatedNodes)) { + return false; + } + ControlFlowGraph cfg = activatedNodes[0].getLookup().lookup(ControlFlowGraph.class); + return cfg.hasHir() || cfg.hasState() || cfg.hasLir(); + } + + public String getName() { + return "Open Intermediate Representation"; + } + + @Override + protected String iconResource() { + return Icons.IR; + } + + protected int mode() { + return CookieAction.MODE_EXACTLY_ONE; + } + + protected Class[] cookieClasses() { + return new Class[]{ControlFlowGraph.class}; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + protected boolean asynchronous() { + return false; + } +} diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/icons/Icons.java b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/icons/Icons.java new file mode 100644 index 000000000000..c8397cb97d27 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/icons/Icons.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.icons; + +/** + * + * @author Christian Wimmer + */ +public class Icons { + private static final String PATH = "at/ssw/visualizer/ir/icons/"; + + public static final String IR = PATH + "ir.gif"; + public static final String EXPAND_HIR = PATH + "expandhir.gif"; + public static final String EXPAND_LIR = PATH + "expandlir.gif"; + public static final String EXPAND_ALL = PATH + "expandall.gif"; + public static final String COLLAPSE_ALL = PATH + "collapseall.gif"; + + private Icons() { + } +} diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRExample b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRExample new file mode 100644 index 000000000000..502547435938 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRExample @@ -0,0 +1,13 @@ +B0 <- B6 -> B7,B1 dom B6 [0, 6] std + Locals size 6 [virtual jint java.lang.String.hashCode()] + 0 a6 + __bci__use__tid__result___instr___________________________ (HIR) + . 1 4 i7 [R58|I] a6._20 (I) + 6 2 i8 0 + . 6 0 v9 if i7 != i8 then B7 else B1 + __nr___instr______________________________________________ (LIR) + 8 label [label:0x2d692b4] + 10 move [Base:[R57|L] Disp: 20|I] [R58|I] + 12 cmp [R58|I] [int:0|I] + 14 branch [NE] [B7] + 16 branch [AL] [B1] diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRScanner.java b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRScanner.java new file mode 100644 index 000000000000..0490a19b7c33 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRScanner.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.model; + +import at.ssw.visualizer.texteditor.model.Scanner; +import org.netbeans.editor.TokenID; + +/** + * Splits the textual intermediate representation into tokens for HIR and LIR operands. + * + * @author Christian Wimmer + */ +public class IRScanner extends Scanner { + public IRScanner() { + super("\n\r\t .,;()[]", IRTokenContext.contextPath); + } + + private boolean isBlock() { + return expectChar('B') && expectChar(DIGIT) && expectChars(DIGIT) && expectEnd(); + } + + private boolean isHir() { + return expectChar(LETTER) && expectChar(DIGIT) && expectChars(DIGIT) && expectEnd(); + } + + private boolean isLir() { + return expectChar(LETTER) && skipUntil('|') && expectChars(LETTER) + && isReferenceOrEmpty() && expectEnd(); + } + + private boolean isReferenceOrEmpty() { + if (ch != '[') + return true; + readNext(); + return expectChars(REFERENCE_CHARS) && expectChar(']'); + } + + @Override + protected TokenID parseToken() { + findTokenBegin(); + if (ch == EOF && offset + 1 >= stopOffset) { + // only except EOF if we are at the end of the buffer + return IRTokenContext.EOF_TOKEN; + } else if (isWhitespace()) { + return IRTokenContext.WHITESPACE_TOKEN; + } else if (isBlock()) { + return IRTokenContext.BLOCK_TOKEN; + } else if (isHir()) { + return IRTokenContext.HIR_TOKEN; + } else if (isLir()) { + return IRTokenContext.LIR_TOKEN; + } else { + readToWhitespace(); + return IRTokenContext.OTHER_TOKEN; + } + } + +} diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRTextBuilder.java b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRTextBuilder.java new file mode 100644 index 000000000000..978ac9ec435f --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRTextBuilder.java @@ -0,0 +1,507 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.model; + +import at.ssw.visualizer.ir.IREditorSupport; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import at.ssw.visualizer.model.Compilation; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.model.cfg.IRInstruction; +import at.ssw.visualizer.model.cfg.State; +import at.ssw.visualizer.model.cfg.StateEntry; +import at.ssw.visualizer.texteditor.model.BlockRegion; +import at.ssw.visualizer.texteditor.model.FoldingRegion; +import at.ssw.visualizer.texteditor.model.HoverParser; +import at.ssw.visualizer.texteditor.model.Text; +import at.ssw.visualizer.texteditor.model.TextBuilder; +import at.ssw.visualizer.texteditor.model.TextRegion; +import java.text.DateFormat; +import java.util.Arrays; +import org.netbeans.api.editor.fold.FoldType; +import org.netbeans.editor.TokenID; + +/** + * + * @author Christian Wimmer + */ +public class IRTextBuilder extends TextBuilder { + + public static final FoldType KIND_BLOCK = new FoldType("..."); + public static final FoldType KIND_STATE_WITH_PHIS = new FoldType("(State)"); + public static final FoldType KIND_STATE_WITHOUT_PHIS = new FoldType("(State)"); + public static final FoldType KIND_HIR = new FoldType("(HIR)"); + public static final FoldType KIND_LIR = new FoldType("(LIR)"); + public static final FoldType KIND_MULTILINE = new FoldType("..."); + private String[] hirColumnNames; + private int[] hirColumnStarts; + private String[] lirColumnNames; + private int[] lirColumnStarts; + + public IRTextBuilder() { + super(); + scanner = new IRScanner(); + } + + public String buildState(ControlFlowGraph cfg, BasicBlock[] blocks) { + defineColumns(cfg); + + Set blockSet = new HashSet(Arrays.asList(blocks)); + for (BasicBlock block : cfg.getBasicBlocks()) { + if (block.hasState() && blockSet.contains(block)) { + appendBlockDetails(block); + text.append("\n"); + appendStates(block); + text.append("\n"); + } + } + if (text.length() == 0) { + return "No State available\n"; + } + return text.toString(); + } + + public String buildHir(ControlFlowGraph cfg, BasicBlock[] blocks) { + defineColumns(cfg); + + Set blockSet = new HashSet(Arrays.asList(blocks)); + for (BasicBlock block : cfg.getBasicBlocks()) { + if (block.hasHir() && blockSet.contains(block)) { + appendBlockDetails(block); + text.append("\n"); + appendHir(block); + text.append("\n"); + } + } + if (text.length() == 0) { + return "No HIR available\n"; + } + return text.toString(); + } + + public String buildLir(ControlFlowGraph cfg, BasicBlock[] blocks) { + defineColumns(cfg); + + Set blockSet = new HashSet(Arrays.asList(blocks)); + for (BasicBlock block : cfg.getBasicBlocks()) { + if (block.hasLir() && blockSet.contains(block)) { + appendBlockDetails(block); + text.append("\n"); + appendLir(block); + text.append("\n"); + } + } + if (text.length() == 0) { + return "No LIR available\n"; + } + return text.toString(); + } + + public Text buildDocument(ControlFlowGraph cfg) { + Compilation compilation = cfg.getCompilation(); + DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); + + text.append(compilation.getMethod()).append("\n"); + text.append(dateFormat.format(compilation.getDate())).append("\n"); + text.append(cfg.getName()).append("\n\n"); + + defineColumns(cfg); + + for (BasicBlock block : cfg.getBasicBlocks()) { + appendBlock(cfg, block); + } + return buildText(cfg, IREditorSupport.MIME_TYPE); + } + + protected void defineColumns(ControlFlowGraph cfg) { + ArrayList hirColumnNames = new ArrayList(); + ArrayList hirColumnWidths = new ArrayList(); + ArrayList lirColumnNames = new ArrayList(); + ArrayList lirColumnWidths = new ArrayList(); + + for (BasicBlock block : cfg.getBasicBlocks()) { + if (cfg.hasHir()) { + defineColumns(block.getHirInstructions(), hirColumnNames, hirColumnWidths); + } + if (cfg.hasLir()) { + defineColumns(block.getLirOperations(), lirColumnNames, lirColumnWidths); + } + } + + this.hirColumnNames = hirColumnNames.toArray(new String[hirColumnNames.size()]); + this.hirColumnStarts = new int[this.hirColumnNames.length]; + int start = 1; + for (int i = 0; i < this.hirColumnStarts.length; i++) { + this.hirColumnStarts[i] = start; + start += hirColumnWidths.get(i) + 2; + } + this.lirColumnNames = lirColumnNames.toArray(new String[lirColumnNames.size()]); + this.lirColumnStarts = new int[this.lirColumnNames.length]; + start = 1; + for (int i = 0; i < this.lirColumnStarts.length; i++) { + this.lirColumnStarts[i] = start; + start += lirColumnWidths.get(i) + 2; + } + + } + + protected void defineColumns(List instructions, List columnNames, List columnWidths) { + for (IRInstruction instr : instructions) { + int prevIdx = -1; + for (String name : instr.getNames()) { + int idx = columnNames.indexOf(name); + + int width = Math.min(HoverParser.firstLine(instr.getValue(name)).length(), 15); + + if (idx == -1) { + width = Math.max(width, name.length()); + columnNames.add(prevIdx + 1, name); + columnWidths.add(prevIdx + 1, width); + prevIdx = prevIdx + 1; + } else { + int oldWidth = columnWidths.get(idx); + if (width > oldWidth) { + columnWidths.set(idx, width); + } + prevIdx = idx; + } + } + } + } + + protected void buildHighlighting() { + // scan the entire content string because it is easier than + // computing information during construction of the string + HashMap> highlightingLists = new HashMap>(); + scanner.setText(text.toString(), 0, text.length()); + TokenID token = scanner.nextToken(); + while (token != null && token.getNumericID() != IRTokenContext.EOF_TOKEN_ID) { + if (token.getNumericID() == IRTokenContext.HIR_TOKEN_ID || token.getNumericID() == IRTokenContext.LIR_TOKEN_ID || token.getNumericID() == IRTokenContext.BLOCK_TOKEN_ID) { + String key = scanner.getTokenString(); + List list = highlightingLists.get(key); + if (list == null) { + list = new ArrayList(); + highlightingLists.put(key, list); + } + list.add(new TextRegion(scanner.getTokenOffset(), scanner.getOffset())); //getTokenStart() gteTokenEnd() + } + token = scanner.nextToken(); + } + + for (String key : highlightingLists.keySet()) { + List list = highlightingLists.get(key); + highlighting.put(key, list.toArray(new TextRegion[list.size()])); + } + } + + private void appendBlock(ControlFlowGraph cfg, BasicBlock block) { + int start = text.length(); + List blockFoldings = new ArrayList(); + + appendBlockDetails(block); + int bodyStart = text.length(); + text.append("\n"); + + if (block.hasState()) { + blockFoldings.add(appendStates(block)); + } + if (block.hasHir()) { + blockFoldings.add(appendHir(block)); + } + if (block.hasLir()) { + blockFoldings.add(appendLir(block)); + } + + // record foldings (no nested foldings if only one detail block is present) + if (blockFoldings.size() > 0) { + text.append(" \n"); + foldingRegions.add(new FoldingRegion(KIND_BLOCK, bodyStart, text.length() - 1, true)); + if (blockFoldings.size() > 1) { + for (FoldingRegion folding : blockFoldings) { + foldingRegions.add(folding); + } + } + } + + // record definition and hyperlink target + recordBlock(block, new TextRegion(start, start + block.getName().length())); + + // record block boundary information + blocks.put(block, new BlockRegion(block, start, text.length(), start, start + block.getName().length())); + } + + private void appendBlockDetails(StringBuilder sb, BasicBlock block) { + sb.append(blockDetails(block)); + } + + private void recordBlock(BasicBlock block, TextRegion hyperlinkTarget) { + StringBuilder blockText = new StringBuilder(); + appendBlockDetails(blockText, block); + recordDefinition(block.getName(), blockText.toString(), hyperlinkTarget); + } + + private FoldingRegion appendStates(BasicBlock block) { + boolean hasPhiOperands = false; + int start = text.length(); + + if (block.hasState()) { + for (State state : block.getStates()) { + hasPhiOperands |= appendState(block, state); + } + } + + return new FoldingRegion(hasPhiOperands ? KIND_STATE_WITH_PHIS : KIND_STATE_WITHOUT_PHIS, start, text.length(), !hasPhiOperands); + } + + private boolean appendState(BasicBlock block, State state) { + boolean hasPhiOperands = false; + + text.append(" ").append(state.getKind()).append(" size ").append(state.getSize()); + if (state.getMethod().length() > 0) { + text.append(" [").append(state.getMethod()).append("]"); + } + text.append("\n"); + + for (StateEntry entry : state.getEntries()) { + int lineStart = text.length(); + text.append(" "); + append(entry.getIndex(), 5); + append(entry.getName(), 5); + + if (appendPhiOperands(text, block, entry)) { + if (entry.getOperand() != null) { + text.append(" - ").append(entry.getOperand()); + } + + hasPhiOperands = true; + TextRegion hyperlinkTarget = new TextRegion(lineStart, text.length()); + recordPhiFunction(block, entry, hyperlinkTarget); + } + text.append("\n"); + } + + return hasPhiOperands; + } + + private void recordPhiFunction(BasicBlock block, StateEntry entry, TextRegion hyperlinkTarget) { + StringBuilder sb = new StringBuilder(); + sb.append(block.getName()).append(" - ").append(entry.getName()); + if (entry.getOperand() != null) { + sb.append(" ").append(entry.getOperand()); + } + sb.append(" : "); + + int start = sb.length(); + appendPhiOperands(sb, block, entry); + String s = sb.toString(); + + recordDefinition(entry.getName(), s, hyperlinkTarget); + if (entry.getOperand() != null) { + recordDefinition(entry.getOperand().substring(1, entry.getOperand().length() - 1), s, hyperlinkTarget); + } + recordUses(s, start, s.length() - start); + } + + private boolean appendPhiOperands(StringBuilder sb, BasicBlock block, StateEntry entry) { + if (entry.hasPhiOperands()) { + sb.append("["); + appendList(sb, "", entry.getPhiOperands()); + sb.append("]"); + return true; + } else if (block.getPredecessors().size() == 0) { + sb.append("[method parameter]"); + return true; + } else { + return false; + } + } + + private FoldingRegion appendHir(BasicBlock block) { + int start = text.length(); + appendColumnHeader(hirColumnNames, hirColumnStarts, " (HIR)"); + for (IRInstruction instruction : block.getHirInstructions()) { + int lineStart = text.length(); + appendColumn(instruction, hirColumnNames, hirColumnStarts); + TextRegion hyperlinkTarget = new TextRegion(lineStart, text.length() - 1); + recordHir(block, instruction, hyperlinkTarget); + } + + return new FoldingRegion(KIND_HIR, start, text.length(), false); + } + + protected void appendColumnHeader(String[] columnNames, int[] columnStarts, String descr) { + int lineStart = text.length(); + for (int i = 0; i < columnNames.length; i++) { + fillTo(columnStarts[i], lineStart, '_'); + text.append(columnNames[i]); + } + fillTo(80, lineStart, '_'); + text.append(descr).append("\n"); + } + + protected void appendColumn(IRInstruction instruction, String[] columnNames, int[] columnStarts) { + int lineStart = text.length(); + int foldStart = -1; + for (int i = 0; i < columnNames.length; i++) { + fillTo(columnStarts[i], lineStart, ' '); + String val = instruction.getValue(columnNames[i]); + if (val != null) { + HoverParser p = new HoverParser(val); + + while (p.hasNext()) { + int start = text.length(); + text.append(p.next()); + if (p.getHover() != null) { + regionHovers.put(new TextRegion(start, text.length()), p.getHover()); + } + if (p.isNewLine()) { + if (foldStart == -1) { + foldStart = text.length() - 1; + } + lineStart = text.length(); + fillTo(columnStarts[i], lineStart, ' '); + } + } + } + } + if (foldStart != -1) { + foldingRegions.add(new FoldingRegion(KIND_MULTILINE, foldStart, text.length(), true)); + } + text.append("\n"); + } + + private void recordHir(BasicBlock block, IRInstruction hir, TextRegion hyperlinkTarget) { + String irName = hir.getValue(IRInstruction.HIR_NAME); + String irText = HoverParser.firstLine(hir.getValue(IRInstruction.HIR_TEXT)); + String irOperand = hir.getValue(IRInstruction.HIR_OPERAND); + + StringBuilder sb = new StringBuilder(); + sb.append(block.getName()).append(" - ").append(irName); + if (irOperand != null) { + sb.append(" ").append(irOperand); + } + sb.append(" : "); + + int start = sb.length(); + sb.append(irText); + String s = sb.toString(); + + recordDefinition(irName, s, hyperlinkTarget); + if (irOperand != null) { + recordDefinition(irOperand.substring(1, irOperand.length() - 1), s, hyperlinkTarget); + } + recordUses(s, start, s.length() - start); + } + + private FoldingRegion appendLir(BasicBlock block) { + int start = text.length(); + appendColumnHeader(lirColumnNames, lirColumnStarts, " (LIR)"); + for (IRInstruction instruction : block.getLirOperations()) { + appendColumn(instruction, lirColumnNames, lirColumnStarts); + recordLir(block, instruction); + } + + return new FoldingRegion(KIND_LIR, start, text.length() - 1, true); + } + + private void recordLir(BasicBlock block, IRInstruction lir) { + String irNumber = lir.getValue(IRInstruction.LIR_NUMBER); + String irText = HoverParser.firstLine(lir.getValue(IRInstruction.LIR_TEXT)); + StringBuilder sb = new StringBuilder(); + sb.append(block.getName()).append(" - ").append(irNumber).append(" "); + + int start = sb.length(); + sb.append(irText); + + recordUses(sb.toString(), start, sb.length() - start); + } + + private void fillTo(int pos, int lineStart, char ch) { + for (int i = pos + lineStart - text.length(); i > 0; i--) { + text.append(ch); + } + } + + private void append(int value, int minLen) { + int oldLen = text.length(); + text.append(value); + + text.append(' '); + for (int i = oldLen + minLen - text.length(); i > 0; i--) { + text.append(' '); + } + } + + private void append(String value, int minLen) { + int oldLen = text.length(); + text.append(value); + text.append(' '); + for (int i = oldLen + minLen - text.length(); i > 0; i--) { + text.append(' '); + } + } + + private void appendList(StringBuilder sb, String prefix, List values) { + for (String value : values) { + sb.append(prefix).append(value); + prefix = ","; + } + } + + private void recordDefinition(String key, String value, TextRegion hyperlinkTarget) { + if (hoverDefinitions.containsKey(key)) { + System.out.println("WARNING: duplicate definition of '" + key + "': '" + value + "' and '" + hoverDefinitions.get(key) + "'"); + } + + hoverKeys.add(key); + hoverDefinitions.put(key, value); + hyperlinks.put(key, hyperlinkTarget); + } + + private void recordUse(String key, String value) { + hoverKeys.add(key); + List list = hoverReferences.get(key); + if (list == null) { + list = new ArrayList(); + hoverReferences.put(key, list); + } + if (!list.contains(value)) { + list.add(value); + } + } + + private void recordUses(String value, int offset, int length) { + scanner.setText(value, offset, length); + TokenID token = scanner.nextToken(); + while (token != null && token.getNumericID() != IRTokenContext.EOF_TOKEN_ID) { + if (token.getNumericID() == IRTokenContext.HIR_TOKEN_ID || token.getNumericID() == IRTokenContext.LIR_TOKEN_ID || token.getNumericID() == IRTokenContext.BLOCK_TOKEN_ID) { + recordUse(scanner.getTokenString(), value); + } + token = scanner.nextToken(); + } + } +} diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRTokenContext.java b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRTokenContext.java new file mode 100644 index 000000000000..8b236ff52ef2 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRTokenContext.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.model; + +import org.netbeans.editor.BaseTokenID; +import org.netbeans.editor.TokenContext; +import org.netbeans.editor.TokenContextPath; +import org.netbeans.editor.TokenID; + +/** + * The one and only IR token context containing all possible tokens. + * + * @author Bernhard Stiftner + */ +public class IRTokenContext extends TokenContext { + + public static final int BLOCK_TOKEN_ID = 1; + public static final int HIR_TOKEN_ID = 2; + public static final int LIR_TOKEN_ID = 3; + public static final int OTHER_TOKEN_ID = -1; + public static final int WHITESPACE_TOKEN_ID = -2; + public static final int EOF_TOKEN_ID = -3; + + public static final TokenID BLOCK_TOKEN = new BaseTokenID("block", BLOCK_TOKEN_ID); + public static final TokenID HIR_TOKEN = new BaseTokenID("hir", HIR_TOKEN_ID); + public static final TokenID LIR_TOKEN = new BaseTokenID("lir", LIR_TOKEN_ID); + public static final TokenID OTHER_TOKEN = new BaseTokenID("other", OTHER_TOKEN_ID); + public static final TokenID WHITESPACE_TOKEN = new BaseTokenID("whitespace", WHITESPACE_TOKEN_ID); + public static final TokenID EOF_TOKEN = new BaseTokenID("eof", EOF_TOKEN_ID); + + public static final IRTokenContext context = new IRTokenContext(); + public static final TokenContextPath contextPath = context.getContextPath(); + + private IRTokenContext() { + super("ir-"); + addTokenID(BLOCK_TOKEN); + addTokenID(HIR_TOKEN); + addTokenID(LIR_TOKEN); + addTokenID(OTHER_TOKEN); + addTokenID(WHITESPACE_TOKEN); + addTokenID(EOF_TOKEN); + } +} diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..70a41b326cd1 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.ir +OpenIDE-Module-Layer: at/ssw/visualizer/ir/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/ir/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/Bundle.properties b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/Bundle.properties new file mode 100644 index 000000000000..f4dc89e61a5f --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/Bundle.properties @@ -0,0 +1,9 @@ +OpenIDE-Module-Name=Intermediate Code Editor + +text/x-compilation-ir=Intermediate Representation +ir-block=Block +ir-lir=LIR +ir-hir=HIR +ir-other=Other +ir-whitespace=Whitespace +ir-default=Default diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/icons/collapseall.gif b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/icons/collapseall.gif new file mode 100644 index 0000000000000000000000000000000000000000..a2d80a9044f38833cb728a69c88294ce3fd007c7 GIT binary patch literal 157 zcmZ?wbhEHb6krfw*v!DtJ#F>UjfWZCs($|cfA#bKkH7!F`St(Z@BiQa{{Qv=|DXRL zz<>l4f3h$#FmN;IfW$y%FtB(Pob+71*X+evXI>YLE;&}Fj8#mRE%&W?B30shyu13% zpT6C#3k-fJGjKF52@24V6I?%GvcZa|)%y<^9(-F=IB9W`k6g3(YLhfsMh0sDZC^x! literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/icons/expandall.gif b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/icons/expandall.gif new file mode 100644 index 0000000000000000000000000000000000000000..0205b29176d4e60307639b6ac80ebfc40be61c3b GIT binary patch literal 164 zcmZ?wbhEHb6krfw*v!DtJ#F>UjfWZCs($|cfA#bKkH7!F`St(Z@Bg3w{Qvg%|F8f5 z|NI951{hHM$->CMz{8*e5&)UOz!Es&r043rW-rb<^SWqs$+0qJtYW%sxo^!AsVxqd z4Ij(xarn?aXSKr@L2f6e289eO){ZAef}c8;L~h|Nj5^|Nrm*|Ns7j00YrL@h1x>1A`!g4oC;cP6m!D24xNzj|~eBHnTBn z#GEj2ILN@FhkG4kiyczkaUV=!U0tm zRzX*>89C}ML&;yLz~L$*BZpG2ki|`d0}V3+1)Zi! Qh$Y_G#C~3ggM+~u05$Pkm;e9( literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/icons/expandlir.gif b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/icons/expandlir.gif new file mode 100644 index 0000000000000000000000000000000000000000..c61bca4b620d31e7707490b9e9b93c8eb5d7c4e4 GIT binary patch literal 246 zcmZ?wbhEHb6krfwIKsfd=vLJ|ZS~PpSC5{)diC@FS3m#1`St(Z@Bbfv|Ns2w|F^&Y zfByad>;L~h|Nj5^|Nrm*|Ns7j00YrL@h1x>1A`!g4oC;cP6m!*24xNzj|~eBHnTBn z#GEj2ILN@FhkG4kiyczkaUV=!U0tm zRzX*>8=T+J JX{Nwn4FJ@fTXX;b literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/icons/ir.gif b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/icons/ir.gif new file mode 100644 index 0000000000000000000000000000000000000000..17f927e9a4e24ec37908bdde84d7525cd3207bb1 GIT binary patch literal 200 zcmZ?wbhEHb6krfwIKse?U)G}5;9=Pwq{}~7Y#h)yU3=AR+Iv|B0I~iDA6;y*b z7WQOh*-Q>zQovvu$grGQS@-Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/model/NetBeans-IR-fontsColors.xml b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/model/NetBeans-IR-fontsColors.xml new file mode 100644 index 000000000000..1c8feb680c07 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/model/NetBeans-IR-fontsColors.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/pom.xml b/visualizer/C1Visualizer/IntermediateCodeViews/pom.xml new file mode 100644 index 000000000000..d6a24bb93958 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/pom.xml @@ -0,0 +1,117 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + IntermediateCodeViews + 1.13-SNAPSHOT + nbm + IntermediateCodeViews + + UTF-8 + + + + at.ssw.visualizer + VisualizerUI + ${project.version} + + + at.ssw.visualizer + IntermediateCodeEditor + ${project.version} + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + at.ssw.visualizer + TextEditor + ${project.version} + + + org.netbeans.api + org-netbeans-modules-editor + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-lib + ${netbeans.version} + + + org.netbeans.api + org-openide-filesystems + ${netbeans.version} + + + org.netbeans.api + org-openide-loaders + ${netbeans.version} + + + org.netbeans.api + org-openide-nodes + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.ir.view + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/HIRViewTopComponent.java b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/HIRViewTopComponent.java new file mode 100644 index 000000000000..0460c057b666 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/HIRViewTopComponent.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.view; + +import at.ssw.visualizer.ir.IREditorKit; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.ir.model.IRTextBuilder; +import at.ssw.visualizer.texteditor.view.AbstractTextViewTopComponent; +import java.io.Serializable; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; + +/** + * TopComponent displaying the HIR view. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + */ +final class HIRViewTopComponent extends AbstractTextViewTopComponent { + + private HIRViewTopComponent() { + super(new IREditorKit()); + setName("HIR"); + setToolTipText("High-level Intermediate Representation"); + } + + @Override + protected String getContent(ControlFlowGraph cfg, BasicBlock[] blocks) { + IRTextBuilder builder = new IRTextBuilder(); + return builder.buildHir(cfg, blocks); + } + + // + private static final String PREFERRED_ID = "HIRViewTopComponent"; + private static HIRViewTopComponent instance; + + public static synchronized HIRViewTopComponent getDefault() { + if (instance == null) { + instance = new HIRViewTopComponent(); + } + return instance; + } + + public static synchronized HIRViewTopComponent findInstance() { + return (HIRViewTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID); + } + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_ALWAYS; + } + + @Override + protected String preferredID() { + return PREFERRED_ID; + } + + @Override + public Object writeReplace() { + return new ResolvableHelper(); + } + + static final class ResolvableHelper implements Serializable { + private static final long serialVersionUID = 1L; + public Object readResolve() { + return HIRViewTopComponent.getDefault(); + } + } + // +} diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/LIRViewTopComponent.java b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/LIRViewTopComponent.java new file mode 100644 index 000000000000..42452370265d --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/LIRViewTopComponent.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.view; + +import at.ssw.visualizer.ir.IREditorKit; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.ir.model.IRTextBuilder; +import at.ssw.visualizer.texteditor.view.AbstractTextViewTopComponent; +import java.io.Serializable; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; + +/** + * TopComponent displaying the LIR view. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + */ +final class LIRViewTopComponent extends AbstractTextViewTopComponent { + private LIRViewTopComponent() { + super(new IREditorKit()); + setName("LIR"); + setToolTipText("Low-level Intermediate Representation"); + } + + @Override + protected String getContent(ControlFlowGraph cfg, BasicBlock[] blocks) { + IRTextBuilder builder = new IRTextBuilder(); + return builder.buildLir(cfg, blocks); + } + + // + private static final String PREFERRED_ID = "LIRViewTopComponent"; + private static LIRViewTopComponent instance; + + public static synchronized LIRViewTopComponent getDefault() { + if (instance == null) { + instance = new LIRViewTopComponent(); + } + return instance; + } + + public static synchronized LIRViewTopComponent findInstance() { + return (LIRViewTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID); + } + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_ALWAYS; + } + + @Override + protected String preferredID() { + return PREFERRED_ID; + } + + @Override + public Object writeReplace() { + return new ResolvableHelper(); + } + + static final class ResolvableHelper implements Serializable { + private static final long serialVersionUID = 1L; + public Object readResolve() { + return LIRViewTopComponent.getDefault(); + } + } + // +} diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/ShowHIRViewAction.java b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/ShowHIRViewAction.java new file mode 100644 index 000000000000..caa1b07dac13 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/ShowHIRViewAction.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.view; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.openide.windows.TopComponent; + +/** + * Action which shows the HIRView component. + * + * @author Bernhard Stiftner + */ +public class ShowHIRViewAction extends AbstractAction { + public ShowHIRViewAction() { + super("HIR View"); + } + + public void actionPerformed(ActionEvent event) { + TopComponent win = HIRViewTopComponent.findInstance(); + win.open(); + win.requestActive(); + } +} diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/ShowLIRViewAction.java b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/ShowLIRViewAction.java new file mode 100644 index 000000000000..2bf5600b0c09 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/ShowLIRViewAction.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.view; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.openide.windows.TopComponent; + +/** + * Action which shows the LIRView component. + * + * @author Bernhard Stiftner + */ +public class ShowLIRViewAction extends AbstractAction { + public ShowLIRViewAction() { + super("LIR View"); + } + + public void actionPerformed(ActionEvent event) { + TopComponent win = LIRViewTopComponent.findInstance(); + win.open(); + win.requestActive(); + } +} diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/ShowStateViewAction.java b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/ShowStateViewAction.java new file mode 100644 index 000000000000..408a47ee2595 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/ShowStateViewAction.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.view; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.openide.windows.TopComponent; + +/** + * Action which shows the StateView component. + * + * @author Bernhard Stiftner + */ +public class ShowStateViewAction extends AbstractAction { + public ShowStateViewAction() { + super("State View"); + } + + public void actionPerformed(ActionEvent event) { + TopComponent win = StateViewTopComponent.findInstance(); + win.open(); + win.requestActive(); + } +} diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/StateViewTopComponent.java b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/StateViewTopComponent.java new file mode 100644 index 000000000000..54346725aa27 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/java/at/ssw/visualizer/ir/view/StateViewTopComponent.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.ir.view; + +import at.ssw.visualizer.ir.IREditorKit; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.ir.model.IRTextBuilder; +import at.ssw.visualizer.texteditor.view.AbstractTextViewTopComponent; +import java.io.Serializable; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; + +/** + * TopComponent displaying the state view. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + */ +final class StateViewTopComponent extends AbstractTextViewTopComponent { + private StateViewTopComponent() { + super(new IREditorKit()); + setName("State"); + setToolTipText("State of Local Variables and Operand Stack"); + } + + @Override + protected String getContent(ControlFlowGraph cfg, BasicBlock[] blocks) { + IRTextBuilder builder = new IRTextBuilder(); + return builder.buildState(cfg, blocks); + } + + // + private static final String PREFERRED_ID = "StateViewTopComponent"; + private static StateViewTopComponent instance; + + public static synchronized StateViewTopComponent getDefault() { + if (instance == null) { + instance = new StateViewTopComponent(); + } + return instance; + } + + public static synchronized StateViewTopComponent findInstance() { + return (StateViewTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID); + } + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_ALWAYS; + } + + @Override + protected String preferredID() { + return PREFERRED_ID; + } + + @Override + public Object writeReplace() { + return new ResolvableHelper(); + } + + static final class ResolvableHelper implements Serializable { + private static final long serialVersionUID = 1L; + public Object readResolve() { + return StateViewTopComponent.getDefault(); + } + } + // +} diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..0108407a2e84 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.ir.view +OpenIDE-Module-Layer: at/ssw/visualizer/ir/view/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/ir/view/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/Bundle.properties b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/Bundle.properties new file mode 100644 index 000000000000..1cd5dfc8c788 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Intermediate Code Views diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/HIRViewTopComponentSettings.xml b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/HIRViewTopComponentSettings.xml new file mode 100644 index 000000000000..dab039f8a03c --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/HIRViewTopComponentSettings.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/HIRViewTopComponentWstcref.xml b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/HIRViewTopComponentWstcref.xml new file mode 100644 index 000000000000..7edd9db7c96c --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/HIRViewTopComponentWstcref.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/LIRViewTopComponentSettings.xml b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/LIRViewTopComponentSettings.xml new file mode 100644 index 000000000000..692444020fb0 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/LIRViewTopComponentSettings.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/LIRViewTopComponentWstcref.xml b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/LIRViewTopComponentWstcref.xml new file mode 100644 index 000000000000..cb35fee1ac5d --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/LIRViewTopComponentWstcref.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/StateViewTopComponentSettings.xml b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/StateViewTopComponentSettings.xml new file mode 100644 index 000000000000..965772b3eb16 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/StateViewTopComponentSettings.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/StateViewTopComponentWstcref.xml b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/StateViewTopComponentWstcref.xml new file mode 100644 index 000000000000..4ba9933876d5 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/StateViewTopComponentWstcref.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/layer.xml b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/layer.xml new file mode 100644 index 000000000000..581cc8a53218 --- /dev/null +++ b/visualizer/C1Visualizer/IntermediateCodeViews/src/main/resources/at/ssw/visualizer/ir/view/layer.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/IntervalEditor/pom.xml b/visualizer/C1Visualizer/IntervalEditor/pom.xml new file mode 100644 index 000000000000..1d62d1af26f2 --- /dev/null +++ b/visualizer/C1Visualizer/IntervalEditor/pom.xml @@ -0,0 +1,107 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + IntervalEditor + 1.13-SNAPSHOT + nbm + IntervalEditor + + UTF-8 + + + + at.ssw.visualizer + VisualizerUI + ${project.version} + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + at.ssw.visualizer + TextEditor + ${project.version} + + + org.netbeans.api + org-openide-awt + ${netbeans.version} + + + org.netbeans.api + org-openide-loaders + ${netbeans.version} + + + org.netbeans.api + org-openide-nodes + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-util-ui + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.model.interval + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/IntervalCanvas.java b/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/IntervalCanvas.java new file mode 100644 index 000000000000..80ce76c5ff76 --- /dev/null +++ b/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/IntervalCanvas.java @@ -0,0 +1,577 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.interval; + +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.model.cfg.IRInstruction; +import at.ssw.visualizer.model.interval.ChildInterval; +import at.ssw.visualizer.model.interval.Interval; +import at.ssw.visualizer.model.interval.IntervalList; +import at.ssw.visualizer.model.interval.Range; +import at.ssw.visualizer.model.interval.UsePosition; +import at.ssw.visualizer.texteditor.model.HoverParser; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionAdapter; +import java.awt.event.MouseMotionListener; +import java.awt.geom.Rectangle2D; +import java.util.List; +import javax.swing.JComponent; +import javax.swing.JViewport; +import javax.swing.Scrollable; +import javax.swing.SwingConstants; +import javax.swing.UIManager; + +/** + * A Viewport showing an interval chart. Intended to be used in conjunction + * with a JScrollPane. + * + * @author Christian Wimmer + * @author Bernhard Stiftner + */ +public class IntervalCanvas extends JViewport { + + class ViewData { + + /** number of instructions/intervals hidden on left/top */ + public int offsetCol; + public int offsetRow; + /** number of instructions/intervals visible */ + public int sizeCol; + public int sizeRow; + /** total number of instructions/intervals */ + public int totalRow; + public int totalCol; + public int fontHeight; + public int fontAscent; + public int mouseX; + public int mouseY; + public int selectedCol; + public int selectedRow; + /** offset in pixels where first visible col/row starts */ + public int offsetX; + public int offsetY; + /** size in pixels of viewable area */ + public int sizeX; + public int sizeY; + public Interval selectedInterval; + public ChildInterval selectedChild; + public BasicBlock selectedBlock; + public IRInstruction selectedOperation; + + public int colToX(int col) { + return (col - offsetCol) * viewSettings.colWidth + offsetX; + } + + public int rowToY(int row) { + return (row - offsetRow) * viewSettings.rowHeight + offsetY; + } + } + + /** + * Just a dummy component which is used as the view of this viewport. + * It's just here to perform some layout calculations; it never gets painted. + */ + class PlaceHolder extends JComponent implements Scrollable { + + public PlaceHolder() { + setEnabled(false); + } + + public Dimension getPreferredScrollableViewportSize() { + return getPreferredSize(); + } + + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + switch (orientation) { + case SwingConstants.HORIZONTAL: + return 2 * viewSettings.colWidth; + case SwingConstants.VERTICAL: + return viewSettings.rowHeight; + } + throw new RuntimeException("illegal orientation"); + } + + public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + switch (orientation) { + case SwingConstants.HORIZONTAL: + return viewSettings.colWidth * viewData.sizeCol * 3 / 8; + case SwingConstants.VERTICAL: + return viewSettings.rowHeight * viewData.sizeRow * 3 / 4; + } + throw new RuntimeException("illegal orientation"); + } + + public boolean getScrollableTracksViewportWidth() { + return false; + } + + public boolean getScrollableTracksViewportHeight() { + return false; + } + } + private IntervalEditorTopComponent editor; + private IntervalList intervals; + private ViewSettings viewSettings; + private ViewData viewData = new ViewData(); + /** inserted as view, just used for layout purposes */ + private PlaceHolder placeholder = new PlaceHolder(); + private MouseMotionListener mouseMotionListener = new MouseMotionAdapter() { + + @Override + public void mouseMoved(MouseEvent e) { + calcSelection(e.getX(), e.getY()); + } + }; + private MouseListener mouseListener = new MouseAdapter() { + + @Override + public void mouseEntered(MouseEvent e) { + calcSelection(e.getX(), e.getY()); + } + + @Override + public void mouseExited(MouseEvent e) { + calcSelection(-1, -1); + } + + @Override + public void mouseReleased(MouseEvent e) { + editor.updateCanvasSelection(); + } + }; + private ComponentListener componentListener = new ComponentAdapter() { + + @Override + public void componentResized(ComponentEvent e) { + calcViewData(); + } + + @Override + public void componentShown(ComponentEvent e) { + calcViewData(); + } + }; + + public IntervalCanvas(IntervalEditorTopComponent editor, ViewSettings viewSettings, IntervalList intervals) { + this.editor = editor; + this.intervals = intervals; + this.viewSettings = viewSettings; + + setView(placeholder); + setOpaque(true); + setBackground(UIManager.getColor("TextPane.background")); + setFocusable(true); + + addMouseListener(mouseListener); + addMouseMotionListener(mouseMotionListener); + addComponentListener(componentListener); + } + + public IntervalList getIntervals() { + return intervals; + } + + public BasicBlock getSelectedBlock() { + return viewData.selectedBlock; + } + + public ChildInterval getSelectedInterval() { + return viewData.selectedChild; + } + + public void ensureColumnVisible(int column) { + if (column < viewData.offsetCol) { + setViewPosition(new Point(column * viewSettings.colWidth, getViewPosition().y)); + } else if (column >= viewData.offsetCol + viewData.sizeCol - 4) { + setViewPosition(new Point((column - viewData.sizeCol + 4) * viewSettings.colWidth, getViewPosition().y)); + } + } + + public void ensureRowVisible(int row) { + if (row < viewData.offsetRow) { + setViewPosition(new Point(getViewPosition().x, row * viewSettings.rowHeight)); + } else if (row >= viewData.offsetRow + viewData.sizeRow - 1) { + setViewPosition(new Point(getViewPosition().x, (row - viewData.sizeRow + 1) * viewSettings.rowHeight)); + } + } + + public void calcViewData() { + + Graphics gc = getGraphics(); + if (gc == null) { + return; + } + + Dimension ca = getExtentSize(); + + viewData.totalRow = intervals.getIntervals().size(); + viewData.totalCol = intervals.getNumLIROperations(); + + gc.setFont(viewSettings.textFont); + FontMetrics fm = gc.getFontMetrics(); + viewData.fontHeight = fm.getHeight(); + viewData.fontAscent = fm.getAscent(); + viewData.offsetX = fm.stringWidth("v" + viewData.totalRow + "|a") + 15; + + viewData.offsetY = viewData.fontHeight * 2; + + placeholder.setPreferredSize(new Dimension(viewData.offsetX + (viewData.totalCol + 2) * viewSettings.colWidth + 2 + viewSettings.thickLineWidth, viewData.offsetY + (viewData.totalRow + 1) * viewSettings.rowHeight + 2)); + placeholder.setSize(placeholder.getPreferredSize()); + + viewData.offsetCol = getViewPosition().x / viewSettings.colWidth / 2 * 2; + viewData.offsetRow = getViewPosition().y / viewSettings.rowHeight; + + viewData.sizeCol = Math.min((ca.width - viewData.offsetX - 3) / viewSettings.colWidth / 2 * 2, viewData.totalCol - viewData.offsetCol); + viewData.sizeRow = Math.min((ca.height - viewData.offsetY - 3) / viewSettings.rowHeight, viewData.totalRow - viewData.offsetRow); + + viewData.sizeX = viewData.sizeCol * viewSettings.colWidth; + viewData.sizeY = viewData.sizeRow * viewSettings.rowHeight; + + calcSelection(); + repaint(); + } + + public void calcSelection(int mouseX, int mouseY) { + viewData.mouseX = mouseX; + viewData.mouseY = mouseY; + calcSelection(); + } + + public void calcSelection() { + int newCol; + int newRow; + if (viewData.mouseX != -1 && viewData.mouseY != -1) { + newCol = (viewData.mouseX - viewData.offsetX + 2 + viewSettings.colWidth * 2) / viewSettings.colWidth / 2 * 2 + viewData.offsetCol - 2; + newRow = (viewData.mouseY - viewData.offsetY + 2 + viewSettings.rowHeight) / viewSettings.rowHeight + viewData.offsetRow - 1; + } else { + newCol = -1; + newRow = -1; + } + + if (newCol < viewData.offsetCol || newCol >= viewData.offsetCol + viewData.sizeCol) { + newCol = -1; + } + if (newRow < viewData.offsetRow || newRow >= viewData.offsetRow + viewData.sizeRow) { + newRow = -1; + } + + if (viewData.selectedCol != newCol || viewData.selectedRow != newRow) { + viewData.selectedCol = newCol; + viewData.selectedRow = newRow; + + if (newRow != -1) { + viewData.selectedInterval = intervals.getIntervals().get(newRow); + viewData.selectedChild = getChildByLirId(viewData.selectedInterval, newCol); + } else { + viewData.selectedInterval = null; + viewData.selectedChild = null; + } + + if (newCol != -1) { + viewData.selectedBlock = getBlockByLirId(intervals.getControlFlowGraph(), newCol); + viewData.selectedOperation = getOperationByLirId(viewData.selectedBlock, newCol); + } else { + viewData.selectedBlock = null; + viewData.selectedOperation = null; + } + + repaint(); + updateStatusLine(); + } + } + + private BasicBlock getBlockByLirId(ControlFlowGraph cfg, int lirId) { + // TODO could do binary search + for (BasicBlock basicBlock : cfg.getBasicBlocks()) { + if (basicBlock.getFirstLirId() <= lirId && lirId <= basicBlock.getLastLirId()) { + return basicBlock; + } + } + return null; + } + + private IRInstruction getOperationByLirId(BasicBlock block, int lirId) { + if (block == null) { + return null; + } + // TODO could do binary search + for (IRInstruction operation : block.getLirOperations()) { + try { + if (Integer.parseInt(operation.getValue(IRInstruction.LIR_NUMBER)) == lirId) { + return operation; + } + } catch (NumberFormatException ex) { + // Silently ignore wrong numbers. + } + } + return null; + } + + public ChildInterval getChildByLirId(Interval interval, int lirId) { + if (interval == null) { + return null; + } + // TODO could do binary search + List children = interval.getChildren(); + for (ChildInterval child : children) { + for (Range range : child.getRanges()) { + if (range.getFrom() <= lirId && range.getTo() >= lirId) { + return child; + } + } + } + return children.get(0); + } + + private void updateStatusLine() { + String intervalText = ""; + String instructionText = ""; + String blockText = ""; + + ChildInterval child = viewData.selectedChild; + if (child != null) { + intervalText = child.getRegNum() + " " + child.getType() + " " + child.getOperand(); + } + + IRInstruction operation = viewData.selectedOperation; + if (operation != null) { + for (String name : operation.getNames()) { + instructionText = instructionText + HoverParser.firstLine(operation.getValue(name)) + " "; + } + } + + BasicBlock block = viewData.selectedBlock; + if (block != null) { + if (block.getLoopDepth() > 0) { + blockText = block.getName() + " (loop " + block.getLoopIndex() + " depth " + block.getLoopDepth() + ")"; + } else { + blockText = block.getName(); + } + } + + editor.setIntervalStatusText(intervalText); + editor.setInstructionStatusText(instructionText); + editor.setBlockStatusText(blockText); + } + + private int gridStart(int start, int grid) { + return grid - 1 - (start + grid - 1) % grid; + } + + private void drawXGrid(Graphics gc, int grid, Color color) { + gc.setColor(color); + int y1 = viewData.offsetY; + int y2 = viewData.offsetY + viewData.sizeY; + + for (int i = gridStart(viewData.offsetCol, grid); i < viewData.sizeCol; i += grid) { + int x = viewData.offsetX + i * viewSettings.colWidth; + gc.drawLine(x, y1, x, y2); + } + } + + private void drawXText(Graphics gc, int grid, Color color) { + gc.setColor(color); + int y = 0; + + for (int i = gridStart(viewData.offsetCol, grid); i < viewData.sizeCol; i += grid) { + int x = viewData.offsetX + i * viewSettings.colWidth + viewSettings.thickLineWidth + 2; + gc.drawString(String.valueOf(viewData.offsetCol + i), x, viewData.fontAscent + y); + } + } + + private void drawYGrid(Graphics gc, int grid, Color color) { + gc.setColor(color); + int x1 = viewData.offsetX; + int x2 = viewData.offsetX + viewData.sizeX; + + for (int i = gridStart(viewData.offsetRow, grid); i < viewData.sizeRow; i += grid) { + int y = viewData.offsetY + i * viewSettings.rowHeight; + gc.drawLine(x1, y, x2, y); + } + } + + private void drawYText(Graphics gc, int grid, Color color) { + gc.setColor(color); + int x = 5; + + for (int i = gridStart(viewData.offsetRow, grid); i < viewData.sizeRow; i += grid) { + Interval interval = intervals.getIntervals().get(i + viewData.offsetRow); + if (interval != null) { + int y = viewData.offsetY + i * viewSettings.rowHeight; + gc.drawString(String.valueOf(interval.getRegNum()), x, viewData.fontAscent + y); + } + } + } + + private void drawBorder(Graphics gc) { + gc.setColor(viewSettings.darkGridColor); + gc.drawRect(viewData.offsetX, viewData.offsetY, viewData.sizeX, viewData.sizeY); + } + + private void drawBlocks(Graphics gc) { + int y1 = viewData.fontHeight; + int y2 = viewData.offsetY + viewData.sizeY; + + for (BasicBlock basicBlock : intervals.getControlFlowGraph().getBasicBlocks()) { + if (basicBlock.getFirstLirId() >= viewData.offsetCol && basicBlock.getFirstLirId() <= viewData.offsetCol + viewData.sizeCol) { + int x = viewData.colToX(basicBlock.getFirstLirId()); + gc.setColor(viewSettings.darkGridColor); + gc.fillRect(x, y1, viewSettings.thickLineWidth, y2 - y1); + + if (basicBlock.getFirstLirId() < viewData.offsetCol + viewData.sizeCol) { + String text = basicBlock.getName(); + if (basicBlock.getLoopDepth() > 0) { + text += " (" + basicBlock.getLoopDepth() + ")"; + } + gc.setColor(getBackground()); + Rectangle2D stringBounds = gc.getFontMetrics().getStringBounds(text, gc); + gc.fillRect(x + viewSettings.thickLineWidth + 2, y1, (int) stringBounds.getWidth(), (int) stringBounds.getHeight()); + gc.setColor(viewSettings.textColor); + gc.drawString(text, x + viewSettings.thickLineWidth + 2, viewData.fontAscent + y1); + } + } + } + + gc.setColor(viewSettings.darkGridColor); + if (intervals.getNumLIROperations() <= viewData.offsetCol + viewData.sizeCol) { + int x = viewData.colToX(intervals.getNumLIROperations()); + gc.fillRect(x, y1, viewSettings.thickLineWidth, y2 - y1); + } + } + + public void drawInterval(Graphics gc, ChildInterval interval, int barY, int barHeight, int textY) { + gc.setColor(viewSettings.getIntervalColor(interval)); + + int textX = -1; + for (Range range : interval.getRanges()) { + if (range.getTo() > viewData.offsetCol && range.getFrom() < viewData.offsetCol + viewData.sizeCol) { + int x1 = Math.max(viewData.colToX(range.getFrom()) + viewSettings.thickLineWidth, viewData.offsetX); + int x2 = Math.min(viewData.colToX(range.getTo()), viewData.offsetX + viewData.sizeX); + gc.fillRect(x1, barY, x2 - x1, barHeight); + + if (textX == -1) { + textX = x1; + } + } + } + + for (UsePosition usePosition : interval.getUsePositions()) { + if (usePosition.getPosition() >= viewData.offsetCol && usePosition.getPosition() < viewData.offsetCol + viewData.sizeCol) { + gc.setColor(viewSettings.getUsePosColor(usePosition)); + + int x = viewData.colToX(usePosition.getPosition()); + gc.fillRect(x, barY, viewSettings.thickLineWidth, barHeight); + } + } + + gc.setColor(viewSettings.textColor); + if (viewSettings.showIntervalText && textX != -1) { + textX = Math.max(textX, viewData.offsetX + viewSettings.thickLineWidth); + String text = String.valueOf(interval.getRegNum()) + " " + interval.getOperand(); + gc.drawString(text, textX + 1, viewData.fontAscent + textY); + } + } + + public void drawIntervals(Graphics gc) { + gc.setColor(viewSettings.textColor); + int barHeight = viewSettings.rowHeight - viewSettings.barSeparation * 2 - 1; + + for (int i = 0; i < viewData.sizeRow; i++) { + Interval interval = intervals.getIntervals().get(i + viewData.offsetRow); + int textY = viewData.offsetY + i * viewSettings.rowHeight; + int barY = textY + 1 + viewSettings.barSeparation; + + for (ChildInterval child : interval.getChildren()) { + drawInterval(gc, child, barY, barHeight, textY); + } + } + } + + private void drawSelection(Graphics gc) { + gc.setColor(Color.RED); + Dimension ca = getExtentSize(); + + if (viewData.selectedCol != -1) { + int x = viewData.colToX(viewData.selectedCol); + gc.fillRect(x, 0, 3, ca.height); + } + if (viewData.selectedRow != -1) { + int y = viewData.rowToY(viewData.selectedRow); + gc.fillRect(0, y - 1, ca.width, 3); + } + } + + public void drawAll(Graphics gc) { + gc.setColor(getBackground()); + gc.fillRect(0, 0, getWidth(), getHeight()); + + gc.setFont(viewSettings.textFont); + + if (viewSettings.lightGridX > 0) { + drawXGrid(gc, viewSettings.lightGridX, viewSettings.lightGridColor); + } + if (viewSettings.lightGridY > 0) { + drawYGrid(gc, viewSettings.lightGridY, viewSettings.lightGridColor); + } + if (viewSettings.darkGridX > 0) { + drawXGrid(gc, viewSettings.darkGridX, viewSettings.darkGridColor); + } + if (viewSettings.darkGridY > 0) { + drawYGrid(gc, viewSettings.darkGridY, viewSettings.darkGridColor); + } + if (viewSettings.textGridX > 0) { + drawXText(gc, viewSettings.textGridX, viewSettings.textColor); + } + if (viewSettings.textGridY > 0) { + drawYText(gc, viewSettings.textGridY, viewSettings.textColor); + } + drawBorder(gc); + drawBlocks(gc); + drawIntervals(gc); + drawSelection(gc); + } + + @Override + public void paint(Graphics g) { + g.clearRect(0, 0, getSize().width, getSize().height); + drawAll(g); + } + + @Override + protected void fireStateChanged() { + calcViewData(); + super.fireStateChanged(); + } +} diff --git a/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/IntervalEditorSupport.java b/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/IntervalEditorSupport.java new file mode 100644 index 000000000000..dcc6f5b4c06b --- /dev/null +++ b/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/IntervalEditorSupport.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.interval; + +import at.ssw.visualizer.model.interval.IntervalList; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.beans.VetoableChangeListener; +import java.beans.VetoableChangeSupport; +import java.io.IOException; +import org.openide.cookies.OpenCookie; +import org.openide.windows.CloneableOpenSupport; +import org.openide.windows.CloneableTopComponent; + +/** + * Support class for opening interval editors. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + */ +public class IntervalEditorSupport extends CloneableOpenSupport implements OpenCookie { + private IntervalList intervalList; + + public IntervalEditorSupport(IntervalList intervalList) { + super(new Env()); + ((Env) env).editorSupport = this; + this.intervalList = intervalList; + } + + protected CloneableTopComponent createCloneableTopComponent() { + return new IntervalEditorTopComponent(intervalList); + } + + public String messageOpened() { + return "Opened " + intervalList.getCompilation().getMethod() + " - " + intervalList.getName(); + } + + public String messageOpening() { + return "Opening " + intervalList.getCompilation().getMethod() + " - " + intervalList.getName(); + } + + + public static class Env implements CloneableOpenSupport.Env { + private PropertyChangeSupport prop = new PropertyChangeSupport(this); + private VetoableChangeSupport veto = new VetoableChangeSupport(this); + private IntervalEditorSupport editorSupport; + + public boolean isValid() { + return true; + } + + public boolean isModified() { + return false; + } + + public void markModified() throws IOException { + throw new IOException("Editor is readonly"); + } + + public void unmarkModified() { + // Nothing to do. + } + + public CloneableOpenSupport findCloneableOpenSupport() { + return editorSupport; + } + + public void addPropertyChangeListener(PropertyChangeListener l) { + prop.addPropertyChangeListener(l); + } + + public void removePropertyChangeListener(PropertyChangeListener l) { + prop.removePropertyChangeListener(l); + } + + public void addVetoableChangeListener(VetoableChangeListener l) { + veto.addVetoableChangeListener(l); + } + + public void removeVetoableChangeListener(VetoableChangeListener l) { + veto.removeVetoableChangeListener(l); + } + } +} diff --git a/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/IntervalEditorTopComponent.java b/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/IntervalEditorTopComponent.java new file mode 100644 index 000000000000..53b4b2545784 --- /dev/null +++ b/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/IntervalEditorTopComponent.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.interval; + +import at.ssw.visualizer.core.selection.Selection; +import at.ssw.visualizer.core.selection.SelectionManager; +import at.ssw.visualizer.core.selection.SelectionProvider; +import at.ssw.visualizer.interval.icons.Icons; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.model.interval.ChildInterval; +import at.ssw.visualizer.model.interval.IntervalList; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JToggleButton; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.openide.awt.Toolbar; +import org.openide.util.ImageUtilities; +import org.openide.windows.CloneableTopComponent; +import org.openide.windows.TopComponent; + +/** + * TopComponent for displaying interval diagrams. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + */ +public final class IntervalEditorTopComponent extends CloneableTopComponent implements SelectionProvider { + private static final String HSIZE_PROP = "visualizer.hsize"; + private static final String VSIZE_PROP = "visualizer.vsize"; + + private IntervalCanvas canvas; + private ViewSettings viewSettings; + private JToggleButton[] hsizeButtons; + private JToggleButton[] vsizeButtons; + private JLabel intervalStatusLabel; + private JLabel blockStatusLabel; + private JLabel instructionStatusLabel; + + private IntervalList intervals; + private Selection selection; + private boolean selectionUpdating; + + protected IntervalEditorTopComponent(IntervalList intervals) { + setName(intervals.getCompilation().getShortName()); + setToolTipText(intervals.getCompilation().getMethod() + " - " + intervals.getName()); + setIcon(ImageUtilities.loadImage(Icons.INTERVALS)); + + this.intervals = intervals; + selection = new Selection(); + selection.put(intervals); + selection.put(intervals.getControlFlowGraph()); + selection.addChangeListener(selectionChangeListener); + + viewSettings = new ViewSettings(); + canvas = new IntervalCanvas(this, viewSettings, intervals); + JScrollPane scrollPane = new JScrollPane(canvas); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + scrollPane.setViewportBorder(BorderFactory.createEmptyBorder()); + + hsizeButtons = new JToggleButton[3]; + hsizeButtons[0] = createSizeButton(Icons.HSIZE_SMALL, "Horizontal Size: Small", HSIZE_PROP, ViewSettings.SMALL); + hsizeButtons[1] = createSizeButton(Icons.HSIZE_MEDIUM, "Horizontal Size: Medium", HSIZE_PROP, ViewSettings.MEDIUM); + hsizeButtons[2] = createSizeButton(Icons.HSIZE_LARGE, "Horizontal Size: Large", HSIZE_PROP, ViewSettings.LARGE); + vsizeButtons = new JToggleButton[3]; + vsizeButtons[0] = createSizeButton(Icons.VSIZE_SMALL, "Vertical Size: Small", VSIZE_PROP, ViewSettings.SMALL); + vsizeButtons[1] = createSizeButton(Icons.VSIZE_MEDIUM, "Vertical Size: Medium", VSIZE_PROP, ViewSettings.MEDIUM); + vsizeButtons[2] = createSizeButton(Icons.VSIZE_LARGE, "Vertical Size: Large", VSIZE_PROP, ViewSettings.LARGE); + Toolbar toolbar = new Toolbar(); + toolbar.setBorder((Border) UIManager.get("Nb.Editor.Toolbar.border")); + toolbar.add(hsizeButtons[0]); + toolbar.add(hsizeButtons[1]); + toolbar.add(hsizeButtons[2]); + toolbar.addSeparator(); + toolbar.add(vsizeButtons[0]); + toolbar.add(vsizeButtons[1]); + toolbar.add(vsizeButtons[2]); + + intervalStatusLabel = createStatusLabel("Nb.Editor.Status.leftBorder", new Dimension(200, 18)); + instructionStatusLabel = createStatusLabel("Nb.Editor.Status.innerBorder", null); + blockStatusLabel = createStatusLabel("Nb.Editor.Status.rightBorder", new Dimension(200, 18)); + JPanel statusBar = new JPanel(new BorderLayout()); + statusBar.add(intervalStatusLabel, BorderLayout.WEST); + statusBar.add(blockStatusLabel, BorderLayout.EAST); + statusBar.add(instructionStatusLabel, BorderLayout.CENTER); + + setLayout(new BorderLayout()); + add(toolbar, BorderLayout.NORTH); + add(scrollPane, BorderLayout.CENTER); + add(statusBar, BorderLayout.SOUTH); + + hsizeButtons[2].doClick(); + vsizeButtons[2].doClick(); + } + + public Selection getSelection() { + return selection; + } + + private JToggleButton createSizeButton(String icon, String tooltip, String propName, int propValue) { + JToggleButton button = new JToggleButton(new ImageIcon(ImageUtilities.loadImage(icon))); + button.setToolTipText(tooltip); + button.putClientProperty(propName, propValue); + button.addActionListener(sizeButtonListener); + return button; + } + + private JLabel createStatusLabel(String border, Dimension dimension) { + JLabel label = new JLabel(" "); + label.setOpaque(true); + label.setBorder((Border) UIManager.get(border)); + if (dimension != null) { + label.setPreferredSize(dimension); + } + return label; + } + + @Override + public void componentActivated() { + super.componentActivated(); + canvas.requestFocus(); + SelectionManager.getDefault().setSelection(selection); + } + + @Override + public void componentClosed() { + super.componentClosed(); + SelectionManager.getDefault().removeSelection(selection); + } + + + private ActionListener sizeButtonListener = new ActionListener() { + public void actionPerformed(ActionEvent event) { + Object hsize = ((JComponent) event.getSource()).getClientProperty(HSIZE_PROP); + if (hsize instanceof Integer) { + viewSettings.setHorizontalSize((Integer) hsize); + } + Object vsize = ((JComponent) event.getSource()).getClientProperty(VSIZE_PROP); + if (vsize instanceof Integer) { + viewSettings.setVerticalSize((Integer) vsize); + } + + canvas.calcViewData(); + + for (int i = 0; i < hsizeButtons.length; i++) { + hsizeButtons[i].setSelected(viewSettings.hsize == i); + } + for (int i = 0; i < vsizeButtons.length; i++) { + vsizeButtons[i].setSelected(viewSettings.vsize == i); + } + } + }; + + + private ChangeListener selectionChangeListener = new ChangeListener() { + public void stateChanged(ChangeEvent event) { + if (selectionUpdating) { + return; + } + selectionUpdating = true; + updateBlockSelection(); + updateIntervalSelection(); + selectionUpdating = false; + } + }; + + private void updateBlockSelection() { + assert selection.get(ControlFlowGraph.class) == intervals.getControlFlowGraph(); + BasicBlock[] blocks = selection.get(BasicBlock[].class); + if (blocks == null || blocks.length == 0 || (blocks.length == 1 && blocks[0] == canvas.getSelectedBlock())) { + return; + } + + int leftCol = Integer.MAX_VALUE; + int rightCol = Integer.MIN_VALUE; + for (BasicBlock block : blocks) { + leftCol = Math.min(leftCol, block.getFirstLirId()); + rightCol = Math.max(rightCol, block.getLastLirId()); + } + canvas.ensureColumnVisible(rightCol); + canvas.ensureColumnVisible(leftCol); + } + + private void updateIntervalSelection() { + assert selection.get(IntervalList.class) == intervals; + ChildInterval child = selection.get(ChildInterval.class); + if (child == null || child == canvas.getSelectedInterval()) { + return; + } + + canvas.ensureColumnVisible(child.getFrom()); + canvas.ensureRowVisible(intervals.getIntervals().indexOf(child.getParent())); + } + + protected void updateCanvasSelection() { + if (selectionUpdating) { + return; + } + selectionUpdating = true; + if (canvas.getSelectedBlock() != null) { + selection.put(new BasicBlock[]{canvas.getSelectedBlock()}); + } + if (canvas.getSelectedInterval() != null) { + selection.put(canvas.getSelectedInterval()); + } + selectionUpdating = false; + } + + + protected void setIntervalStatusText(String text) { + intervalStatusLabel.setText(" " + text); + } + + protected void setBlockStatusText(String text) { + blockStatusLabel.setText(" " + text); + } + protected void setInstructionStatusText(String text) { + instructionStatusLabel.setText(" " + text); + } + + + @Override + protected CloneableTopComponent createClonedObject() { + return new IntervalEditorTopComponent(intervals); + } + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_NEVER; + } +} diff --git a/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/ShowIntervalEditorAction.java b/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/ShowIntervalEditorAction.java new file mode 100644 index 000000000000..96c4df96f27c --- /dev/null +++ b/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/ShowIntervalEditorAction.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.interval; + +import at.ssw.visualizer.core.focus.Focus; +import at.ssw.visualizer.model.interval.IntervalList; +import at.ssw.visualizer.interval.icons.Icons; +import org.openide.nodes.Node; +import org.openide.util.HelpCtx; +import org.openide.util.actions.CookieAction; + +/** + * Opens a new interval visualization window. + * + * @author Bernhard Stiftner + * @author Christian Wimmer + */ +public final class ShowIntervalEditorAction extends CookieAction { + protected void performAction(Node[] activatedNodes) { + IntervalList intervalList = activatedNodes[0].getLookup().lookup(IntervalList.class); + if (!Focus.findEditor(IntervalEditorTopComponent.class, intervalList)) { + IntervalEditorSupport editor = new IntervalEditorSupport(intervalList); + editor.open(); + } + } + + public String getName() { + return "Open Intervals"; + } + + @Override + protected String iconResource() { + return Icons.INTERVALS; + } + + protected int mode() { + return CookieAction.MODE_EXACTLY_ONE; + } + + protected Class[] cookieClasses() { + return new Class[]{IntervalList.class}; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + protected boolean asynchronous() { + return false; + } +} diff --git a/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/ViewSettings.java b/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/ViewSettings.java new file mode 100644 index 000000000000..9348d3523475 --- /dev/null +++ b/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/ViewSettings.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.interval; + +import at.ssw.visualizer.model.interval.ChildInterval; +import at.ssw.visualizer.model.interval.UsePosition; +import java.awt.Color; +import java.awt.Font; +import java.util.HashMap; +import java.util.Map; + +/** + * Encapsulates display parameters for the interval visualization. + * + * @author Christian Wimmer + */ +public class ViewSettings { + public static final int SMALL = 0; + public static final int MEDIUM = 1; + public static final int LARGE = 2; + + // horizontal size + private final int[] _colWidth = {2, 4, 8}; + private final int[] _lightGridX = {-1, 10, 2}; + private final int[] _darkGridX = {-1, -1, 10}; + private final int[] _textGridX = {40, 20, 10}; + private final int[] _thickLineWidth = {2, 2, 3}; + + // vertical size + private final int[] _rowHeight = {3, 6, 14}; + private final int[] _lightGridY = {8, 4, 1}; + private final int[] _darkGridY = {-1, -1, 10}; + private final int[] _textGridY = {8, 4, 1}; + private final int[] _barSeparation = {0, 0, 1}; + + private final boolean[][] _showIntervalText = {{false, false, false}, {false, false, false}, {false, true, true}}; + + private static final Color DEFAULT_INT_COLOR = new Color(255, 255, 102); + + /** current size settings */ + public int hsize; + public int vsize; + + + public int colWidth; + public int rowHeight; + + /** space above and below each interval bar */ + public int barSeparation; + + /** width of block-separator lines, use-positions and indentation of ranges */ + public int thickLineWidth; + + public int lightGridX; + public int darkGridX; + public int textGridX; + public int lightGridY; + public int darkGridY; + public int textGridY; + + boolean showIntervalText; + + public Color lightGridColor; + public Color darkGridColor; + public Color blockGridColor; + public Color textColor; + + public Font textFont; + + private Color usePosColorL; + private Color usePosColorS; + private Color usePosColorM; + private Color usePosColorOther; + private Map typeIntervalColors; + private Color stackIntervalColor; + + + public ViewSettings() { + lightGridColor = Color.GRAY; + darkGridColor = Color.DARK_GRAY; + blockGridColor = Color.DARK_GRAY; + textColor = Color.BLACK; + + usePosColorL = new Color(192, 64, 64); + usePosColorS = new Color(255, 0, 192); + usePosColorM = new Color(255, 0, 0); + usePosColorOther = new Color(0, 255, 255); + + stackIntervalColor = new Color(255, 192, 64); + typeIntervalColors = new HashMap(); + typeIntervalColors.put("fixed", new Color(128, 128, 128)); + typeIntervalColors.put("object", new Color(192, 64, 255)); + typeIntervalColors.put("int", new Color(64, 192, 255)); + typeIntervalColors.put("long", new Color(0, 128, 255)); + typeIntervalColors.put("float", new Color(192, 255, 64)); + typeIntervalColors.put("double", new Color(128, 255, 0)); + + typeIntervalColors.put("byte", new Color(192, 64, 255)); + typeIntervalColors.put("word", new Color(192, 64, 255)); + typeIntervalColors.put("dword", new Color(64, 192, 255)); + typeIntervalColors.put("qword", new Color(0, 128, 255)); + typeIntervalColors.put("single", new Color(192, 255, 64)); + typeIntervalColors.put("double", new Color(128, 255, 0)); + + textFont = new Font("Dialog", Font.PLAIN, 11); + } + + public void setHorizontalSize(int hsize) { + this.hsize = hsize; + update(); + } + + public void setVerticalSize(int vsize) { + this.vsize = vsize; + update(); + } + + + private void update() { + colWidth = _colWidth[hsize]; + rowHeight = _rowHeight[vsize]; + lightGridX = _lightGridX[hsize]; + darkGridX = _darkGridX[hsize]; + textGridX = _textGridX[hsize]; + lightGridY = _lightGridY[vsize]; + darkGridY = _darkGridY[vsize]; + textGridY = _textGridY[vsize]; + thickLineWidth = _thickLineWidth[hsize]; + barSeparation = _barSeparation[vsize]; + showIntervalText = _showIntervalText[vsize][hsize]; + } + + public Color getIntervalColor(ChildInterval child) { + if (child.getOperand().contains("stack")) { + return stackIntervalColor; + } else { + final String typeName = child.getType().toLowerCase(); + final Color color = typeIntervalColors.get(typeName); + if (color == null) { + return DEFAULT_INT_COLOR; + } + return color; + } + } + + public Color getUsePosColor(UsePosition usePos) { + switch (usePos.getKind()) { + case 'L': + return usePosColorL; + case 'S': + return usePosColorS; + case 'M': + return usePosColorM; + default: + //throw new Error("illegal use kind"); + return usePosColorOther; + } + } +} diff --git a/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/icons/Icons.java b/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/icons/Icons.java new file mode 100644 index 000000000000..b6a03563c6a6 --- /dev/null +++ b/visualizer/C1Visualizer/IntervalEditor/src/main/java/at/ssw/visualizer/interval/icons/Icons.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.interval.icons; + +/** + * + * @author Christian Wimmer + */ +public class Icons { + private static final String PATH = "at/ssw/visualizer/interval/icons/"; + + public static final String INTERVALS = PATH + "intervals.gif"; + + public static final String HSIZE_LARGE = PATH + "hsizelarge.gif"; + public static final String HSIZE_MEDIUM = PATH + "hsizemedium.gif"; + public static final String HSIZE_SMALL = PATH + "hsizesmall.gif"; + public static final String VSIZE_LARGE = PATH + "vsizelarge.gif"; + public static final String VSIZE_MEDIUM = PATH + "vsizemedium.gif"; + public static final String VSIZE_SMALL = PATH + "vsizesmall.gif"; + + private Icons() { + } +} diff --git a/visualizer/C1Visualizer/IntervalEditor/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/IntervalEditor/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..a82c399dfbd7 --- /dev/null +++ b/visualizer/C1Visualizer/IntervalEditor/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.interval +OpenIDE-Module-Layer: at/ssw/visualizer/interval/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/interval/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/Bundle.properties b/visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/Bundle.properties new file mode 100644 index 000000000000..d79d161b97f9 --- /dev/null +++ b/visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Interval Editor diff --git a/visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/icons/hsizelarge.gif b/visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/icons/hsizelarge.gif new file mode 100644 index 0000000000000000000000000000000000000000..8afa7e666765473a5b966ec1cc779f3d66a2559c GIT binary patch literal 166 zcmZ?wbhEHb6krfw*v!D-8x@;h){G<^M03(OQAsLMW42NeiGs`5bFn;dWVjK9+Mq2*B C{4wov_K2=INlov2DAUCeXWj``d;4+=ctxhW_>1 zr+yr*h9CRehX4QnA^8LW0018VEC2ui01yBW000G7;3tk`SvpdxN{;zSR4h}H3r%=F zJ?_FZj9@V0Di$x6q|$P6SvJt<#^UN^Fd9$R({Wum;4r6RERzg{w7Ot$6%O$Dq2L-I U6o`8QA#ec#fPn!7W@irH8gtO2=mDo6kT literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/icons/vsizesmall.gif b/visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/icons/vsizesmall.gif new file mode 100644 index 0000000000000000000000000000000000000000..0f30937d5a32b9702f351ec9830e395de3bd35f6 GIT binary patch literal 113 zcmZ?wbhEHb6krfwSj51PU)GXZJ0+%dX-N0x|Ns9pz<}aU7ET5R76u)V07xwZhZzGW zhm6OD1qYki*fnA-92|fWf?jhpCO&j(7hnvybK>G6ryfDeE|(V@A0D2d9L%S~8MOFR H1A{dHww56- literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/layer.xml b/visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/layer.xml new file mode 100644 index 000000000000..ff687d0b50cf --- /dev/null +++ b/visualizer/C1Visualizer/IntervalEditor/src/main/resources/at/ssw/visualizer/interval/layer.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/IntervalView/pom.xml b/visualizer/C1Visualizer/IntervalView/pom.xml new file mode 100644 index 000000000000..2cd220615ea2 --- /dev/null +++ b/visualizer/C1Visualizer/IntervalView/pom.xml @@ -0,0 +1,97 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + IntervalView + 1.13-SNAPSHOT + nbm + IntervalView + + UTF-8 + + + + at.ssw.visualizer + VisualizerUI + ${project.version} + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + org.netbeans.api + org-openide-actions + ${netbeans.version} + + + org.netbeans.api + org-openide-loaders + ${netbeans.version} + + + org.netbeans.api + org-openide-nodes + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.IntervalView + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/IntervalView/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/IntervalView/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..cade72e44f21 --- /dev/null +++ b/visualizer/C1Visualizer/IntervalView/src/main/nbm/manifest.mf @@ -0,0 +1,4 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.interval.view +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/interval/view/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 \ No newline at end of file diff --git a/visualizer/C1Visualizer/IntervalView/src/main/resources/at/ssw/visualizer/interval/view/Bundle.properties b/visualizer/C1Visualizer/IntervalView/src/main/resources/at/ssw/visualizer/interval/view/Bundle.properties new file mode 100644 index 000000000000..5fe72303fd99 --- /dev/null +++ b/visualizer/C1Visualizer/IntervalView/src/main/resources/at/ssw/visualizer/interval/view/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Interval View diff --git a/visualizer/C1Visualizer/NativeCodeEditor/pom.xml b/visualizer/C1Visualizer/NativeCodeEditor/pom.xml new file mode 100644 index 000000000000..2a8cc6d2acc2 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/pom.xml @@ -0,0 +1,128 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + NativeCodeEditor + 1.13-SNAPSHOT + nbm + NativeCodeEditor + + UTF-8 + + + + at.ssw.visualizer + VisualizerUI + ${project.version} + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + at.ssw.visualizer + TextEditor + ${project.version} + + + org.netbeans.api + org-netbeans-modules-editor + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-fold + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-lib + ${netbeans.version} + + + org.netbeans.api + org-openide-awt + ${netbeans.version} + + + org.netbeans.api + org-openide-nodes + ${netbeans.version} + + + org.netbeans.api + org-openide-text + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-util-ui + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + org.netbeans.api + org-netbeans-libs-jna + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.nc + at.ssw.visualizer.nc.model + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 17 + 17 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/NCEditor.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/NCEditor.java new file mode 100644 index 000000000000..97e356751a3c --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/NCEditor.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.nc; + +import at.ssw.visualizer.texteditor.Editor; +import org.openide.windows.CloneableTopComponent; + +/** + * + * @author Alexander Reder + */ +public class NCEditor extends Editor { + + public NCEditor(NCEditorSupport support) { + super(support); + } + + @Override + protected CloneableTopComponent createClonedObject() { + NCEditor editor = new NCEditor((NCEditorSupport) cloneableEditorSupport()); + editor.setActivatedNodes(getActivatedNodes()); + return editor; + } + +} diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/NCEditorKit.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/NCEditorKit.java new file mode 100644 index 000000000000..b0d9f2e1a792 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/NCEditorKit.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.nc; + +import at.ssw.visualizer.nc.model.NCScanner; +import at.ssw.visualizer.texteditor.EditorKit; +import java.util.Collection; +import javax.swing.Action; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import javax.swing.text.TextAction; +import org.netbeans.editor.Syntax; +import org.netbeans.modules.editor.NbEditorKit; +import org.openide.util.Lookup; +import org.openide.util.actions.CallableSystemAction; +import org.openide.util.lookup.Lookups; + +/** + * + * @author Alexander Reder + */ +public class NCEditorKit extends EditorKit { + + @Override + public Syntax createSyntax(Document document) { + return new NCScanner(); + } + + @Override + public String getContentType() { + return NCEditorSupport.MIME_TYPE; + } + + /** + * Add a costum CodeFoldingPopupAction to enable code folding of + * the LIR Comments in the native code. + */ + @Override + protected Action[] getCustomActions() { + return TextAction.augmentList(super.getCustomActions(), new Action[]{ + new GenerateFoldPopupAction() + }); + } + + /** + * Extend the existing GenerateFoldPopupAction with the + * expand and collapse lir comment actions. + */ + public static class GenerateFoldPopupAction extends NbEditorKit.GenerateFoldPopupAction { + + @Override + public JMenuItem getPopupMenuItem(JTextComponent target) { + JMenuItem menu = super.getPopupMenuItem(target); + menu.add(new JPopupMenu.Separator()); + Lookup lookup = Lookups.forPath("Actions/View/NCFolding"); + Collection foldingActions = lookup.lookupAll(CallableSystemAction.class); + for (CallableSystemAction csa : foldingActions) { + menu.add(csa.getMenuPresenter()); + } + return menu; + } + } +} diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/NCEditorSupport.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/NCEditorSupport.java new file mode 100644 index 000000000000..90ca8b992894 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/NCEditorSupport.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.nc; + +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.nc.icons.Icons; +import at.ssw.visualizer.nc.model.NCTextBuilder; +import at.ssw.visualizer.texteditor.EditorSupport; +import org.openide.text.CloneableEditor; +import org.openide.util.ImageUtilities; + + +/** + * + * @author Alexander Reder + */ +public class NCEditorSupport extends EditorSupport { + + public static final String MIME_TYPE = "text/x-compilation-nc"; + + public NCEditorSupport(ControlFlowGraph cfg) { + super(cfg); + this.text = new NCTextBuilder().buildDocument(cfg); + } + + public String getMimeType() { + return MIME_TYPE; + } + + @Override + protected CloneableEditor createCloneableEditor() { + return new NCEditor(this); + } + + @Override + protected void initializeCloneableEditor(CloneableEditor editor) { + super.initializeCloneableEditor(editor); + editor.setIcon(ImageUtilities.loadImage(Icons.NATIVECODE)); + } + +} diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/action/CollapseCommentsAction.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/action/CollapseCommentsAction.java new file mode 100644 index 000000000000..e0059202827b --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/action/CollapseCommentsAction.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.nc.action; + +import at.ssw.visualizer.nc.icons.Icons; +import at.ssw.visualizer.nc.model.NCTextBuilder; +import javax.swing.text.JTextComponent; +import org.netbeans.api.editor.fold.FoldHierarchy; +import org.netbeans.api.editor.fold.FoldUtilities; +import org.netbeans.editor.Utilities; +import org.openide.util.HelpCtx; +import org.openide.util.actions.CallableSystemAction; + +public final class CollapseCommentsAction extends CallableSystemAction { + + public void performAction() { + JTextComponent comp = Utilities.getFocusedComponent(); + FoldHierarchy hierarchy = FoldHierarchy.get(comp); + FoldUtilities.collapse(hierarchy, NCTextBuilder.LIR_BLOCK); + } + + public String getName() { + return "Collapse LIR Comments"; + } + + @Override + protected String iconResource() { + return Icons.COLLAPSE_COMMENTS; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + protected boolean asynchronous() { + return false; + } +} diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/action/ExpandCommentsAction.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/action/ExpandCommentsAction.java new file mode 100644 index 000000000000..53049c1a133e --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/action/ExpandCommentsAction.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.nc.action; + +import at.ssw.visualizer.nc.icons.Icons; +import at.ssw.visualizer.nc.model.NCTextBuilder; +import javax.swing.text.JTextComponent; +import org.netbeans.api.editor.fold.FoldHierarchy; +import org.netbeans.api.editor.fold.FoldUtilities; +import org.netbeans.editor.Utilities; +import org.openide.util.HelpCtx; +import org.openide.util.actions.CallableSystemAction; + +public final class ExpandCommentsAction extends CallableSystemAction { + + public void performAction() { + JTextComponent comp = Utilities.getFocusedComponent(); + FoldHierarchy hierarchy = FoldHierarchy.get(comp); + FoldUtilities.expand(hierarchy, NCTextBuilder.LIR_BLOCK); + } + + public String getName() { + return "Expand LIR Comments"; + } + + @Override + protected String iconResource() { + return Icons.EXPAND_COMMENTS; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + protected boolean asynchronous() { + return false; + } +} diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/action/ShowNCEditorAction.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/action/ShowNCEditorAction.java new file mode 100644 index 000000000000..c3bf9fa19f4b --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/action/ShowNCEditorAction.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.nc.action; + +import at.ssw.visualizer.core.focus.Focus; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.nc.NCEditor; +import at.ssw.visualizer.nc.NCEditorSupport; +import at.ssw.visualizer.nc.icons.Icons; +import org.openide.nodes.Node; +import org.openide.util.HelpCtx; +import org.openide.util.actions.CookieAction; + +/** + * + * @author Alexander Reder + */ +public final class ShowNCEditorAction extends CookieAction { + + protected void performAction(Node[] activatedNodes) { + ControlFlowGraph cfg = activatedNodes[0].getLookup().lookup(ControlFlowGraph.class); + if (!Focus.findEditor(NCEditor.class, cfg)) { + NCEditorSupport support = new NCEditorSupport(cfg); + support.open(); + } + } + + @Override + protected boolean enable(Node[] activatedNodes) { + if(activatedNodes == null || activatedNodes.length == 0) { + return false; + } + ControlFlowGraph cfg = activatedNodes[0].getLookup().lookup(ControlFlowGraph.class); + if(cfg == null) { + return false; + } + return cfg.getNativeMethod() != null; + } + + protected int mode() { + return CookieAction.MODE_EXACTLY_ONE; + } + + public String getName() { + return "Open Native code"; + } + + protected Class[] cookieClasses() { + return new Class[]{ControlFlowGraph.class}; + } + + @Override + protected String iconResource() { + return Icons.NATIVECODE; + } + + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + protected @Override + boolean asynchronous() { + return false; + } +} + diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/icons/Icons.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/icons/Icons.java new file mode 100644 index 000000000000..eafc3c5063d7 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/icons/Icons.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.nc.icons; + +/** + * + * @author Alexander Reder + */ +public class Icons { + private static final String PATH = "at/ssw/visualizer/nc/icons/"; + + public static final String NATIVECODE = PATH + "nativecode.gif"; + public static final String EXPAND_COMMENTS = PATH + "expandlir.gif"; + public static final String COLLAPSE_COMMENTS = PATH + "collapselir.gif"; +} diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/HexCodeFileSupport.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/HexCodeFileSupport.java new file mode 100644 index 000000000000..6595d0099cfd --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/HexCodeFileSupport.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.nc.model; + + +public class HexCodeFileSupport { + public static String decode(String text) { + return com.oracle.max.hcfdis.HexCodeFileDis.processEmbeddedString(text); + } +} diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCExample b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCExample new file mode 100644 index 000000000000..7050d356c549 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCExample @@ -0,0 +1,9 @@ +[Entry Point] +B0 <- B6 -> B2,B1 dom B6 [0, 6] std + ;; 8 label [label:0x9ea954] + ;; 10 move [Base:[ecx|L] Disp: 20|I] [eax|I] + 0x00b0087f: movl 0x14(%ecx),%eax + ;; 12 cmp [eax|I] [int:0|I] + 0x00b00882: cmpl $0x0,%eax + ;; 14 branch [NE] [B2] + 0x00b00885: jne 0xb008ce diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCScanner.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCScanner.java new file mode 100644 index 000000000000..c64b2605096e --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCScanner.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.nc.model; + +import at.ssw.visualizer.texteditor.model.Scanner; +import java.util.HashSet; +import java.util.Set; +import org.netbeans.editor.TokenID; + +/** + * + * @author Alexander Reder + * @author Christian Wimmer + */ +public class NCScanner extends Scanner { + private Set registerNames; + + public NCScanner() { + super("\n\r\t ,;:()$[]", NCTokenContext.contextPath); + + registerNames = new HashSet(); + registerNames.add("eax"); + registerNames.add("ebx"); + registerNames.add("ecx"); + registerNames.add("edx"); + registerNames.add("esi"); + registerNames.add("edi"); + registerNames.add("esp"); + registerNames.add("ebp"); + registerNames.add("rax"); + registerNames.add("rbx"); + registerNames.add("rcx"); + registerNames.add("rdx"); + registerNames.add("rsi"); + registerNames.add("rdi"); + registerNames.add("rsp"); + registerNames.add("rbp"); + registerNames.add("r8"); + registerNames.add("r9"); + registerNames.add("r10"); + registerNames.add("r11"); + registerNames.add("r12"); + registerNames.add("r13"); + registerNames.add("r14"); + registerNames.add("r15"); + registerNames.add("r8d"); + registerNames.add("r9d"); + registerNames.add("r10d"); + registerNames.add("r11d"); + registerNames.add("r12d"); + registerNames.add("r13d"); + registerNames.add("r14d"); + registerNames.add("r15d"); + registerNames.add("xmm0"); + registerNames.add("xmm1"); + registerNames.add("xmm2"); + registerNames.add("xmm3"); + registerNames.add("xmm4"); + registerNames.add("xmm5"); + registerNames.add("xmm6"); + registerNames.add("xmm7"); + registerNames.add("xmm8"); + registerNames.add("xmm9"); + registerNames.add("xmm10"); + registerNames.add("xmm11"); + registerNames.add("xmm12"); + registerNames.add("xmm13"); + registerNames.add("xmm14"); + registerNames.add("xmm15"); + } + + private boolean isBlock() { + return (expectChar('B') && expectChar(DIGIT) && expectChars(DIGIT) && expectEnd()) || + (expectChar('L') && expectChar(DIGIT) && expectChars(DIGIT) && expectEnd()); + } + + private boolean isAddress() { + return expectChar('0') && expectChar('x') && expectChar(HEX) && expectChars(HEX) && expectEnd(); + } + + private boolean isRegister() { + return (expectChar('%') && expectChar(LETTER) && expectChars(LETTER_DIGIT) && expectEnd()) || + isKeyword(registerNames); + } + + private boolean isInstruction() { + boolean result = (beforeChar(':') || beforeChars(DIGIT)) && expectChar(LC_LETTER) && expectChars(LC_LETTER_DIGIT); + while (result && ch == ' ' && offset + 1 < stopOffset && LC_LETTER.get(buffer[offset + 1])) { + readNext(); + result = expectChars(LC_LETTER_DIGIT); + } + return result && expectEnd(); + } + + private boolean isComment() { + int curOffset = offset; + boolean startFound = false; + while (curOffset >= 0 && buffer[curOffset] != '\n') { + if (buffer[curOffset] == ';') { + startFound = true; + tokenOffset = curOffset; + } + curOffset--; + } + if (!startFound) { + return false; + } + while (ch != '\n' && ch != EOF) { + readNext(); + } + return true; + } + + @Override + protected TokenID parseToken() { + findTokenBegin(); + if (ch == EOF) { + return NCTokenContext.EOF_TOKEN; + } else if (isComment()) { + return NCTokenContext.COMMENT_TOKEN; + } else if (isWhitespace()) { + return NCTokenContext.WHITESPACE_TOKEN; + } else if (isBlock()) { + return NCTokenContext.BLOCK_TOKEN; + } else if (isAddress()) { + return NCTokenContext.ADDRESS_TOKEN; + } else if (isRegister()) { + return NCTokenContext.REGISTER_TOKEN; + } else if (isInstruction()) { + return NCTokenContext.INSTRUCTION_TOKEN; + } else { + readToWhitespace(); + return NCTokenContext.OTHER_TOKEN; + } + } + +} diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTextBuilder.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTextBuilder.java new file mode 100644 index 000000000000..7949aabb1342 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTextBuilder.java @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.nc.model; + +import at.ssw.visualizer.model.Compilation; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.nc.NCEditorSupport; +import at.ssw.visualizer.texteditor.model.BlockRegion; +import at.ssw.visualizer.texteditor.model.FoldingRegion; +import at.ssw.visualizer.texteditor.model.HoverParser; +import at.ssw.visualizer.texteditor.model.Text; +import at.ssw.visualizer.texteditor.model.TextBuilder; +import at.ssw.visualizer.texteditor.model.TextRegion; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.netbeans.api.editor.fold.FoldType; +import org.netbeans.editor.TokenID; + +/** + * + * @author Alexander Reder + */ +public class NCTextBuilder extends TextBuilder { + + public static final FoldType KIND_BLOCK = new FoldType("..."); + public static final FoldType LIR_BLOCK = new FoldType(""); + private CodeBlock codeBlock = null; + private CommentBlock commentBlock = null; + + public NCTextBuilder() { + super(); + scanner = new NCScanner(); + } + + public Text buildDocument(ControlFlowGraph cfg) { + Compilation compilation = cfg.getCompilation(); + DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); + + text.append(compilation.getMethod()).append("\n"); + text.append(dateFormat.format(compilation.getDate())).append("\n\n"); + + return buildDocument(cfg, false); + } + + private Text buildDocument(ControlFlowGraph cfg, boolean skipLIR) { + String text = cfg.getNativeMethod().getMethodText().trim(); + if (text.startsWith("<<>>")) { + text = HexCodeFileSupport.decode(text); + } + String[] methodText = text.split("\n"); + + String l; + for (String s : methodText) { + l = s.trim(); + if (l.length() > 0) { + switch (l.charAt(0)) { + case '[': + checkComment(); + append(s); + append("\n"); + break; + case ';': + parseComment(cfg, s, skipLIR); + break; + case '0': + checkComment(); + parseCodeLine(l); + append(l); + append("\n"); + break; + } + } + } + if (codeBlock != null) { + foldingRegions.add(new FoldingRegion(KIND_BLOCK, codeBlock.codeStart + 1, codeBlock.start + codeBlock.length - 1, true)); + } + + return buildText(cfg, NCEditorSupport.MIME_TYPE); + } + + public String buildView(ControlFlowGraph cfg, BasicBlock[] blocks) { + if (cfg.getNativeMethod() == null) { + return "No native code available\n"; + } + Text t = buildDocument(cfg, true); + String contents = t.getText(); + StringBuilder view = new StringBuilder(); + BlockRegion reg; + for (BasicBlock bb : blocks) { + reg = t.getBlocks().get(bb); + if (reg != null) { + view.append(contents.substring(reg.getStart(), reg.getEnd())); + } + } + return view.toString(); + } + + private void parseComment(ControlFlowGraph cfg, String s, boolean skipComments) { + int i = 0; + String l = s.trim(); + while (i < l.length() && (l.charAt(i) == ';' || l.charAt(i) == ' ')) { + i++; + } + if (l.length() > i + 5 && l.substring(i, i + 5).equals("block")) { // Block beginning + if (codeBlock != null) { // append last parsed block + appendBlock(); + } + codeBlock = new CodeBlock(l.substring(i + 6, l.indexOf(' ', i + 6)), cfg); + } else if (l.contains("slow case") && codeBlock != null && codeBlock.name != null) { + appendBlock(); + codeBlock = new CodeBlock(null, cfg); + append(s); + append("\n"); + } else { // any other comment + if (commentBlock == null) { + commentBlock = new CommentBlock(); + } + if (!skipComments) { + append(s); + append("\n"); + commentBlock.length += s.length() + 1; + } + } + } + + private void parseCodeLine(String s) { + int start = text.length() + 2; + boolean refAdr = false; + String addr = null; + scanner.setText(s, 0, s.length()); + TokenID token = scanner.nextToken(); + while (token != null && token != NCTokenContext.EOF_TOKEN) { + switch (token.getNumericID()) { + case NCTokenContext.ADDRESS_TOKEN_ID: + addr = scanner.getTokenString(); + if (refAdr) { + addReference(normalizeAddr(addr), s); + addReference(trimAddr(addr), s); + } else { + hyperlinks.put(trimAddr(addr), new TextRegion(start, start + addr.length())); + hoverKeys.add(normalizeAddr(addr)); + hoverKeys.add(trimAddr(addr)); + addDefinition(normalizeAddr(addr), s); + addDefinition(trimAddr(addr), s); + refAdr = true; + } + break; + case NCTokenContext.REGISTER_TOKEN_ID: + String reg = scanner.getTokenString(); + hoverKeys.add(reg); + addReference(reg, s); + break; + } + token = scanner.nextToken(); + } + } + + private void addReference(String key, String s) { + if (!hoverReferences.containsKey(key)) { + hoverReferences.put(key, new ArrayList()); + } + String bn = codeBlock == null || codeBlock.name == null ? "" : codeBlock.name + ":\t"; + hoverReferences.get(key).add(bn + s.trim()); + } + + private void addDefinition(String key, String s) { + String bn = codeBlock == null || codeBlock.name == null ? "" : codeBlock.name + ":\t"; + hoverDefinitions.put(key, bn + s.trim()); + } + + private static boolean isAddressParseableAsSignedLong(String addr) { + return addr.length() < (16 + 2) && Character.isDigit(addr.charAt(2)); + } + + private String normalizeAddr(String addr) { + if (!isAddressParseableAsSignedLong(addr)) { + return addr; + } + StringBuilder addrString = new StringBuilder(Long.toString(Long.decode(addr), 16)); + for (int i = addrString.length(); i < 8; i++) { + addrString.insert(0, 0); + } + addrString.insert(0, "0x"); + return addrString.toString(); + } + + private String trimAddr(String addr) { + if (!isAddressParseableAsSignedLong(addr)) { + return addr; + } + return "0x" + Long.toString(Long.decode(addr), 16); + } + + private void appendBlock() { + foldingRegions.add(new FoldingRegion(KIND_BLOCK, codeBlock.codeStart, codeBlock.start + codeBlock.length - 1, false)); + if (codeBlock != null && codeBlock.basicBlock != null) { + blocks.put(codeBlock.basicBlock, + new BlockRegion(codeBlock.basicBlock, + codeBlock.start, + codeBlock.start + codeBlock.length, + codeBlock.start, + codeBlock.start + codeBlock.basicBlock.getName().length())); + } + hyperlinks.put(codeBlock.name, new TextRegion(codeBlock.start, codeBlock.start + codeBlock.name.length())); + codeBlock = null; + } + + private void checkComment() { + if (commentBlock != null) { + foldingRegions.add(new FoldingRegion(LIR_BLOCK, commentBlock.start, commentBlock.start + commentBlock.length + 1, true)); + commentBlock = null; + } + } + + private void append(String s) { + HoverParser p = new HoverParser(s); + while (p.hasNext()) { + int start = text.length(); + String part = p.next(); + text.append(part); + if (codeBlock != null) { + codeBlock.length += part.length(); + } + + if (p.getHover() != null) { + regionHovers.put(new TextRegion(start, text.length()), p.getHover()); + } + } + } + + protected void buildHighlighting() { + scanner.setText(text.toString(), 0, text.length()); + TokenID token = scanner.nextToken(); + Map> highlightings = new HashMap>(); + String name; + while (token != null && token != NCTokenContext.EOF_TOKEN) { + switch (token.getNumericID()) { + case NCTokenContext.ADDRESS_TOKEN_ID: + name = trimAddr(scanner.getTokenString()); + if (!highlightings.containsKey(scanner.getTokenString()) && !highlightings.containsKey(name)) { + List tr = new ArrayList(); + highlightings.put(normalizeAddr(name), tr); + highlightings.put(name, tr); + } + highlightings.get(normalizeAddr(name)).add(new TextRegion(scanner.getTokenOffset(), scanner.getOffset())); + highlightings.get(name).add(new TextRegion(scanner.getTokenOffset(), scanner.getOffset())); + break; + case NCTokenContext.BLOCK_TOKEN_ID: + case NCTokenContext.REGISTER_TOKEN_ID: + if (!highlightings.containsKey(scanner.getTokenString())) { + highlightings.put(scanner.getTokenString(), new ArrayList()); + } + highlightings.get(scanner.getTokenString()).add(new TextRegion(scanner.getTokenOffset(), scanner.getOffset())); + break; + } + token = scanner.nextToken(); + } + for (String key : highlightings.keySet()) { + List regions = highlightings.get(key); + highlighting.put(key, regions.toArray(new TextRegion[regions.size()])); + } + } + + private class CodeBlock { + + private String name; + private BasicBlock basicBlock; + private int start; + private int codeStart; + private int length; + + private CodeBlock(String name, ControlFlowGraph cfg) { + this.name = name; + basicBlock = name == null ? null : cfg.getBasicBlockByName(name); + start = text.length(); + if (basicBlock != null) { + appendBlockDetails(basicBlock); + append("\n"); + } + codeStart = text.length() - 1; + length = text.length() - start; + } + } + + private class CommentBlock { + + int start; + int length; + + private CommentBlock() { + start = text.length(); + length = 0; + } + } +} diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTokenContext.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTokenContext.java new file mode 100644 index 000000000000..cbc8c25ffd80 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTokenContext.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.nc.model; + +import org.netbeans.editor.BaseTokenID; +import org.netbeans.editor.TokenContext; +import org.netbeans.editor.TokenContextPath; +import org.netbeans.editor.TokenID; + +/** + * + * @author Alexander Reder + */ +public class NCTokenContext extends TokenContext { + + public static final int EOF_TOKEN_ID = -1; + public static final int WHITESPACE_TOKEN_ID = -2; + public static final int OTHER_TOKEN_ID = -3; + public static final int ADDRESS_TOKEN_ID = 4; + public static final int INSTRUCTION_TOKEN_ID = 5; + public static final int REGISTER_TOKEN_ID = 6; + public static final int BLOCK_TOKEN_ID = 7; + public static final int COMMENT_TOKEN_ID = 8; + + public static final TokenID EOF_TOKEN = new BaseTokenID("eof", EOF_TOKEN_ID); + public static final TokenID WHITESPACE_TOKEN = new BaseTokenID("whitespace", WHITESPACE_TOKEN_ID); + public static final TokenID OTHER_TOKEN = new BaseTokenID("other", OTHER_TOKEN_ID); + public static final TokenID ADDRESS_TOKEN = new BaseTokenID("address", ADDRESS_TOKEN_ID); + public static final TokenID INSTRUCTION_TOKEN = new BaseTokenID("instruction", INSTRUCTION_TOKEN_ID); + public static final TokenID REGISTER_TOKEN = new BaseTokenID("register", REGISTER_TOKEN_ID); + public static final TokenID BLOCK_TOKEN = new BaseTokenID("block", BLOCK_TOKEN_ID); + public static final TokenID COMMENT_TOKEN = new BaseTokenID("comment", COMMENT_TOKEN_ID); + + public static final NCTokenContext context = new NCTokenContext(); + public static final TokenContextPath contextPath = context.getContextPath(); + + private NCTokenContext() { + super("nc-"); + addTokenID(WHITESPACE_TOKEN); + addTokenID(OTHER_TOKEN); + addTokenID(ADDRESS_TOKEN); + addTokenID(INSTRUCTION_TOKEN); + addTokenID(REGISTER_TOKEN); + addTokenID(BLOCK_TOKEN); + addTokenID(COMMENT_TOKEN); + } + +} diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/com/oracle/max/hcfdis/HexCodeFile.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/com/oracle/max/hcfdis/HexCodeFile.java new file mode 100644 index 000000000000..85843a46086b --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/com/oracle/max/hcfdis/HexCodeFile.java @@ -0,0 +1,558 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved. + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +package com.oracle.max.hcfdis; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A HexCodeFile is a textual format for representing a chunk of machine code along with extra + * information that can be used to enhance a disassembly of the code. + *

+ * A pseudo grammar for a HexCodeFile is given below. + * + *

+ *     HexCodeFile ::= Platform Delim HexCode Delim (OptionalSection Delim)*
+ *
+ *     OptionalSection ::= Comment | OperandComment | JumpTable | LookupTable
+ *
+ *     Platform ::= "Platform" ISA WordWidth
+ *
+ *     HexCode ::= "HexCode" StartAddress HexDigits
+ *
+ *     Comment ::= "Comment" Position String
+ *
+ *     OperandComment ::= "OperandComment" Position String
+ *
+ *     EntryFormat ::= 4 | 8 | "OFFSET" | "KEY2_OFFSET"
+ *
+ *     JumpTable ::= "JumpTable" Position EntryFormat Low High
+ *
+ *     LookupTable ::= "LookupTable" Position NPairs KeySize OffsetSize
+ *
+ *     Position, EntrySize, Low, High, NPairs KeySize OffsetSize ::= int
+ *
+ *     Delim := "<||@"
+ * 
+ *

+ * There must be exactly one HexCode and Platform part in a HexCodeFile. The length of HexDigits + * must be even as each pair of digits represents a single byte. + *

+ * Below is an example of a valid Code input: + * + *

+ *
+ *  Platform AMD64 64  <||@
+ *  HexCode 0 e8000000009090904883ec084889842410d0ffff48893c24e800000000488b3c24488bf0e8000000004883c408c3  <||@
+ *  Comment 24 frame-ref-map: +0 {0}
+ *  at java.lang.String.toLowerCase(String.java:2496) [bci: 1]
+ *              |0
+ *     locals:  |stack:0:a
+ *     stack:   |stack:0:a
+ *    <||@
+ *  OperandComment 24 {java.util.Locale.getDefault()}  <||@
+ *  Comment 36 frame-ref-map: +0 {0}
+ *  at java.lang.String.toLowerCase(String.java:2496) [bci: 4]
+ *              |0
+ *     locals:  |stack:0:a
+ *    <||@
+ *  OperandComment 36 {java.lang.String.toLowerCase(Locale)}  <||@
+ *
+ * 
+ */ +public class HexCodeFile { + /** + * Provides extra information about instructions or data at specific positions in the target + * code. This is optional information that can be used to enhance a disassembly of the code. + */ + public abstract static class CodeAnnotation implements Serializable { + private static final long serialVersionUID = 928798596620568715L; + public final int position; + + public CodeAnnotation(int position) { + this.position = position; + } + + public int getPosition() { + return position; + } + } + + /** + * Describes a table of signed offsets embedded in the code. The offsets are relative to the + * starting address of the table. This type of table maybe generated when translating a + * multi-way branch based on a key value from a dense value set (e.g. the {@code tableswitch} + * JVM instruction). + *

+ * The table is indexed by the contiguous range of integers from {@link #low} to {@link #high} + * inclusive. + */ + public static final class JumpTable extends CodeAnnotation { + private static final long serialVersionUID = -6520017513070589383L; + + /** + * Constants denoting the format and size of each entry in a jump table. + */ + public enum EntryFormat { + /** + * Each entry is a 4 byte offset. The base of the offset is platform dependent. + */ + OFFSET(4), + + /** + * Each entry is a secondary key value followed by a 4 byte offset. The base of the + * offset is platform dependent. + */ + KEY2_OFFSET(8); + + EntryFormat(int size) { + this.size = size; + } + + /** + * Gets the size of an entry in bytes. + */ + public final int size; + } + + /** + * The low value in the key range (inclusive). + */ + public final int low; + + /** + * The high value in the key range (inclusive). + */ + public final int high; + + /** + * The size (in bytes) of each table entry. + */ + public final EntryFormat entryFormat; + + public JumpTable(int position, int low, int high, EntryFormat entryFormat) { + super(position); + if (high <= low) { + throw new IllegalArgumentException(String.format("low (%d) is not less than high(%d)", low, high)); + } + this.low = low; + this.high = high; + this.entryFormat = entryFormat; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "@" + position + ": [" + low + " .. " + high + "]"; + } + } + + /** + * Describes a table of key and offset pairs. The offset in each table entry is relative to the + * address of the table. This type of table maybe generated when translating a multi-way branch + * based on a key value from a sparse value set (e.g. the {@code lookupswitch} JVM instruction). + */ + public static final class LookupTable extends CodeAnnotation { + private static final long serialVersionUID = 6797908737446993328L; + /** + * The number of entries in the table. + */ + public final int npairs; + + /** + * The size (in bytes) of entry's key. + */ + public final int keySize; + + /** + * The size (in bytes) of entry's offset value. + */ + public final int offsetSize; + + public LookupTable(int position, int npairs, int keySize, int offsetSize) { + super(position); + this.npairs = npairs; + this.keySize = keySize; + this.offsetSize = offsetSize; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "@" + position + ": [npairs=" + npairs + ", keySize=" + keySize + ", offsetSize=" + offsetSize + "]"; + } + } + + public static final String NEW_LINE = "\n"; + public static final String SECTION_DELIM = " <||@"; + public static final Pattern SECTION = Pattern.compile("(\\S+)\\s+(.*)", Pattern.DOTALL); + public static final Pattern COMMENT = Pattern.compile("(\\d+)\\s+(.*)", Pattern.DOTALL); + public static final Pattern OPERAND_COMMENT = COMMENT; + public static final Pattern JUMP_TABLE = Pattern.compile("(\\d+)\\s+(\\S+)\\s+(-{0,1}\\d+)\\s+(-{0,1}\\d+)\\s*"); + public static final Pattern LOOKUP_TABLE = Pattern.compile("(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s*"); + public static final Pattern HEX_CODE = Pattern.compile("(\\p{XDigit}+)(?:\\s+(\\p{XDigit}*))?"); + public static final Pattern PLATFORM = Pattern.compile("(\\S+)\\s+(\\S+)", Pattern.DOTALL); + + /** + * Delimiter placed before a HexCodeFile when embedded in a string/stream. + */ + public static final String EMBEDDED_HCF_OPEN = "<<> comments = new TreeMap<>(); + + /** + * Map from a machine code position to a comment for the operands of the instruction at the + * position. + */ + public final Map> operandComments = new TreeMap<>(); + + public final byte[] code; + + public final ArrayList jumpTables = new ArrayList<>(); + + public final ArrayList lookupTables = new ArrayList<>(); + + public final String isa; + + public final int wordWidth; + + public final long startAddress; + + public HexCodeFile(byte[] code, long startAddress, String isa, int wordWidth) { + this.code = code; + this.startAddress = startAddress; + this.isa = isa; + this.wordWidth = wordWidth; + } + + /** + * Parses a string in the format produced by {@link #toString()} to produce a + * {@link HexCodeFile} object. + */ + public static HexCodeFile parse(String input, int sourceOffset, String source, String sourceName) { + return new Parser(input, sourceOffset, source, sourceName).hcf; + } + + /** + * Formats this HexCodeFile as a string that can be parsed with {@link #parse}. + */ + @Override + public String toString() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writeTo(baos); + return baos.toString(); + } + + public void writeTo(OutputStream out) { + PrintStream ps = out instanceof PrintStream ? (PrintStream) out : new PrintStream(out); + ps.printf("Platform %s %d %s%n", isa, wordWidth, SECTION_DELIM); + ps.printf("HexCode %x %s %s%n", startAddress, HexCodeFile.hexCodeString(code), SECTION_DELIM); + + for (JumpTable table : jumpTables) { + ps.printf("JumpTable %d %d %d %d %s%n", table.position, table.entryFormat, table.low, table.high, SECTION_DELIM); + } + + for (LookupTable table : lookupTables) { + ps.printf("LookupTable %d %d %d %d %s%n", table.position, table.npairs, table.keySize, table.keySize, SECTION_DELIM); + } + + for (Map.Entry> e : comments.entrySet()) { + int pos = e.getKey(); + for (String comment : e.getValue()) { + ps.printf("Comment %d %s %s%n", pos, comment, SECTION_DELIM); + } + } + + for (Map.Entry> e : operandComments.entrySet()) { + int pos = e.getKey(); + for (String comment : e.getValue()) { + ps.printf("OperandComment %d %s %s%n", pos, comment, SECTION_DELIM); + } + } + ps.flush(); + } + + /** + * Formats a byte array as a string of hex digits. + */ + public static String hexCodeString(byte[] code) { + StringBuilder sb = new StringBuilder(code.length * 2); + for (int b : code) { + String hex = Integer.toHexString(b & 0xff); + if (hex.length() == 1) { + sb.append('0'); + } + sb.append(hex); + } + return sb.toString(); + } + + /** + * Adds a comment to the list of comments for a given position. + */ + public void addComment(int pos, String comment) { + List list = comments.computeIfAbsent(pos, k -> new ArrayList<>()); + list.add(encodeString(comment)); + } + + /** + * Sets an operand comment for a given position. + */ + public void addOperandComment(int pos, String comment) { + List list = operandComments.computeIfAbsent(pos, k -> new ArrayList<>()); + list.add(encodeString(comment)); + } + + /** + * Modifies a string to mangle any substrings matching {@link #SECTION_DELIM}. + */ + public static String encodeString(String string) { + int index; + String s = string; + while ((index = s.indexOf(SECTION_DELIM)) != -1) { + s = s.substring(0, index) + " < |@" + s.substring(index + SECTION_DELIM.length()); + } + return s; + } + + /** + * Helper class to parse a string in the format produced by {@link HexCodeFile#toString()} and + * produce a {@link HexCodeFile} object. + */ + static class Parser { + + final String input; + final String inputSource; + String isa; + int wordWidth; + byte[] code; + long startAddress; + HexCodeFile hcf; + + Parser(String input, int sourceOffset, String source, String sourceName) { + this.input = input; + this.inputSource = sourceName; + parseSections(sourceOffset, source); + } + + void makeHCF() { + if (hcf == null) { + if (isa != null && wordWidth != 0 && code != null) { + hcf = new HexCodeFile(code, startAddress, isa, wordWidth); + } + } + } + + void checkHCF(String section, int offset) { + check(hcf != null, offset, section + " section must be after Platform and HexCode section"); + } + + void check(boolean condition, int offset, String message) { + if (!condition) { + throw error(offset, message); + } + } + + Error error(int offset, String message) { + throw new Error(errorMessage(offset, message)); + } + + String errorMessage(int offset, String message) { + assert offset < input.length(); + InputPos inputPos = filePos(offset); + int lineEnd = input.indexOf(HexCodeFile.NEW_LINE, offset); + int lineStart = offset - inputPos.col; + String line = lineEnd == -1 ? input.substring(lineStart) : input.substring(lineStart, lineEnd); + return String.format("%s:%d: %s%n%s%n%" + (inputPos.col + 1) + "s", inputSource, inputPos.line, message, line, "^"); + } + + static class InputPos { + final int line; + final int col; + + InputPos(int line, int col) { + this.line = line; + this.col = col; + } + } + + InputPos filePos(int index) { + assert input != null; + int lineStart = input.lastIndexOf(HexCodeFile.NEW_LINE, index) + 1; + + int pos = input.indexOf(HexCodeFile.NEW_LINE); + int line = 1; + while (pos > 0 && pos < index) { + line++; + pos = input.indexOf(HexCodeFile.NEW_LINE, pos + 1); + } + return new InputPos(line, index - lineStart); + } + + void parseSections(int offset, String source) { + assert input.startsWith(source, offset); + int index = 0; + int endIndex = source.indexOf(SECTION_DELIM); + while (endIndex != -1) { + while (source.charAt(index) <= ' ') { + index++; + } + String section = source.substring(index, endIndex).trim(); + parseSection(offset + index, section); + index = endIndex + SECTION_DELIM.length(); + endIndex = source.indexOf(SECTION_DELIM, index); + } + } + + int parseInt(int offset, String value) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw error(offset, "Not a valid integer: " + value); + } + } + + void parseSection(int offset, String section) { + if (section.isEmpty()) { + return; + } + assert input.startsWith(section, offset); + Matcher m = HexCodeFile.SECTION.matcher(section); + check(m.matches(), offset, "Section does not match pattern " + HexCodeFile.SECTION); + + String header = m.group(1); + String body = m.group(2); + int headerOffset = offset + m.start(1); + int bodyOffset = offset + m.start(2); + + switch (header) { + case "Platform": + check(isa == null, bodyOffset, "Duplicate Platform section found"); + m = HexCodeFile.PLATFORM.matcher(body); + check(m.matches(), bodyOffset, "Platform does not match pattern " + HexCodeFile.PLATFORM); + isa = m.group(1); + wordWidth = parseInt(bodyOffset + m.start(2), m.group(2)); + makeHCF(); + break; + case "HexCode": + check(code == null, bodyOffset, "Duplicate Code section found"); + m = HexCodeFile.HEX_CODE.matcher(body); + check(m.matches(), bodyOffset, "Code does not match pattern " + HexCodeFile.HEX_CODE); + String hexAddress = m.group(1); + startAddress = new BigInteger(hexAddress, 16).longValue(); + String hexCode = m.group(2); + if (hexCode == null) { + code = new byte[0]; + } else { + check((hexCode.length() % 2) == 0, bodyOffset, "Hex code length must be even"); + code = new byte[hexCode.length() / 2]; + for (int i = 0; i < code.length; i++) { + String hexByte = hexCode.substring(i * 2, (i + 1) * 2); + code[i] = (byte) Integer.parseInt(hexByte, 16); + } + } + makeHCF(); + break; + case "Comment": { + checkHCF("Comment", headerOffset); + m = HexCodeFile.COMMENT.matcher(body); + check(m.matches(), bodyOffset, "Comment does not match pattern " + HexCodeFile.COMMENT); + int pos = parseInt(bodyOffset + m.start(1), m.group(1)); + String comment = m.group(2); + hcf.addComment(pos, comment); + break; + } + case "OperandComment": { + checkHCF("OperandComment", headerOffset); + m = HexCodeFile.OPERAND_COMMENT.matcher(body); + check(m.matches(), bodyOffset, "OperandComment does not match pattern " + HexCodeFile.OPERAND_COMMENT); + int pos = parseInt(bodyOffset + m.start(1), m.group(1)); + String comment = m.group(2); + hcf.addOperandComment(pos, comment); + break; + } + case "JumpTable": { + checkHCF("JumpTable", headerOffset); + m = HexCodeFile.JUMP_TABLE.matcher(body); + check(m.matches(), bodyOffset, "JumpTable does not match pattern " + HexCodeFile.JUMP_TABLE); + int pos = parseInt(bodyOffset + m.start(1), m.group(1)); + JumpTable.EntryFormat entryFormat = parseJumpTableEntryFormat(m, bodyOffset); + int low = parseInt(bodyOffset + m.start(3), m.group(3)); + int high = parseInt(bodyOffset + m.start(4), m.group(4)); + hcf.jumpTables.add(new JumpTable(pos, low, high, entryFormat)); + break; + } + case "LookupTable": { + checkHCF("LookupTable", headerOffset); + m = HexCodeFile.LOOKUP_TABLE.matcher(body); + check(m.matches(), bodyOffset, "LookupTable does not match pattern " + HexCodeFile.LOOKUP_TABLE); + int pos = parseInt(bodyOffset + m.start(1), m.group(1)); + int npairs = parseInt(bodyOffset + m.start(2), m.group(2)); + int keySize = parseInt(bodyOffset + m.start(3), m.group(3)); + int offsetSize = parseInt(bodyOffset + m.start(4), m.group(4)); + hcf.lookupTables.add(new LookupTable(pos, npairs, keySize, offsetSize)); + break; + } + default: + throw error(offset, "Unknown section header: " + header); + } + } + + private JumpTable.EntryFormat parseJumpTableEntryFormat(Matcher m, int bodyOffset) throws Error { + String entryFormatName = m.group(2); + JumpTable.EntryFormat entryFormat; + if ("4".equals(entryFormatName)) { + entryFormat = JumpTable.EntryFormat.OFFSET; + } else if ("8".equals(entryFormatName)) { + entryFormat = JumpTable.EntryFormat.KEY2_OFFSET; + } else { + try { + entryFormat = JumpTable.EntryFormat.valueOf(entryFormatName); + } catch (IllegalArgumentException e) { + throw error(bodyOffset + m.start(2), "Not a valid " + JumpTable.EntryFormat.class.getSimpleName() + " value: " + entryFormatName); + } + } + return entryFormat; + } + + } +} diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/com/oracle/max/hcfdis/HexCodeFileDis.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/com/oracle/max/hcfdis/HexCodeFileDis.java new file mode 100644 index 000000000000..4576c14ad0db --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/com/oracle/max/hcfdis/HexCodeFileDis.java @@ -0,0 +1,529 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved. + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +package com.oracle.max.hcfdis; + +import static com.oracle.max.hcfdis.HexCodeFile.EMBEDDED_HCF_CLOSE; +import static com.oracle.max.hcfdis.HexCodeFile.EMBEDDED_HCF_OPEN; +import static com.oracle.max.hcfdis.HexCodeFileDis.Width.ONE; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Formatter; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import com.oracle.max.hcfdis.HexCodeFile.JumpTable; +import com.oracle.max.hcfdis.HexCodeFile.JumpTable.EntryFormat; + +import capstone.Capstone; + +/** + * Utility for converting a {@link HexCodeFile} to a commented disassembly. + * + * The fully qualified name of {@link #processEmbeddedString(String)} must not change as it's called + * directly by C1Visualizer. + * + * @see "https://ol-bitbucket.us.oracle.com/projects/G/repos/c1visualizer/browse/C1Visualizer/Native%20Code%20Editor/src/at/ssw/visualizer/nc/model/HexCodeFileSupport.java#6" + */ +public class HexCodeFileDis { + + public HexCodeFileDis() { + } + + /** + * Decoding method called by external tools via reflection. + */ + public static String processEmbeddedString(String source) { + if (!source.startsWith(EMBEDDED_HCF_OPEN) || !source.endsWith(EMBEDDED_HCF_CLOSE)) { + throw new IllegalArgumentException("Input string is not in embedded format"); + } + String input = source.substring(EMBEDDED_HCF_OPEN.length(), source.length() - EMBEDDED_HCF_CLOSE.length()); + HexCodeFile hcf = HexCodeFile.parse(input, 0, input, ""); + if (hcf == null) { + throw new InternalError("Malformed HexCodeFile embedded in string"); + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + process(hcf, new PrintStream(buf), true); + return buf.toString(); + } + + /** + * Disassembles all HexCodeFiles embedded in a given input string. + * + * @param input some input containing 0 or more HexCodeFiles + * @param inputName name for the input source to be used in error messages + * @param startDelim the delimiter just before to an embedded HexCodeFile in {@code input} + * @param endDelim the delimiter just after to an embedded HexCodeFile in {@code input} + * @return the value of {@code input} with all embedded HexCodeFiles converted to their + * disassembled form + */ + public static String processAll(String input, String inputName, String startDelim, String endDelim, boolean showComments) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(input.length() * 2); + PrintStream out = new PrintStream(baos); + int hcfCount = 0; + + int codeEnd = 0; + int index; + while ((index = input.indexOf(startDelim, codeEnd)) != -1) { + int codeStart = index + startDelim.length(); + + String copy = input.substring(codeEnd, codeStart); + if (copy.startsWith(endDelim)) { + copy = copy.substring(endDelim.length()); + } + if (copy.endsWith(startDelim)) { + copy = copy.substring(0, copy.length() - startDelim.length()); + } + out.println(copy); + + int endIndex = input.indexOf(endDelim, codeStart); + assert endIndex != -1; + + String source = input.substring(codeStart, endIndex); + + HexCodeFile hcf = HexCodeFile.parse(input, codeStart, source, inputName); + if (hcf == null) { + throw new InternalError("Malformed HexCodeFile in " + inputName); + } + process(hcf, out, showComments); + hcfCount++; + codeEnd = endIndex; + } + + String copy = input.substring(codeEnd); + if (copy.startsWith(endDelim)) { + copy = copy.substring(endDelim.length()); + } + + System.out.println(inputName + ": disassembled " + hcfCount + " embedded HexCodeFiles"); + out.print(copy); + out.flush(); + return baos.toString(); + } + + /** + * Computes the max width of a column based on values to be be printed in the column. + */ + static class Width { + static final Width ONE = new Width(1); + + Width(int initialValue) { + this.value = initialValue; + } + + void update(int i, int radix) { + assert this != ONE; + update(Integer.toString(i, radix)); + } + + void update(String s) { + assert this != ONE; + int len = s.length(); + if (len > value) { + value = len; + } + } + + @Override + public String toString() { + return String.valueOf(value); + } + + int value; + } + + /** + * An entry in a jump table in the decoded instruction stream. + */ + static class JumpTableEntry { + + JumpTableEntry(int offset, long address, int primaryKey, Integer secondaryKey, long target) { + this.offset = offset; + this.address = address; + this.primaryKey = primaryKey; + this.secondaryKey = secondaryKey; + this.target = target; + } + + final int offset; + final long address; + final int primaryKey; + final Integer secondaryKey; + + /** + * Initially a {@code long}, replaced with an {@link Instruction} by + * {@link HexCodeFileDis#updateTargets}. + */ + Object target; + + @Override + public String toString() { + return toString(ONE, ONE, ONE, ONE); + } + + String toString(Width offsetWidth, Width labelWidth, Width primaryKeyWidth, Width secondaryKeyWidth) { + String secondaryKeyString = secondaryKey == null ? "" : String.format(", %-" + secondaryKeyWidth + "s", secondaryKey); + String targetString; + if (target instanceof Instruction) { + Instruction targetInstruction = (Instruction) target; + targetString = targetInstruction.labeledAddress(); + } else { + targetString = String.valueOf(target); + } + return String.format("0x%08x(+0x%-" + offsetWidth + "x): %" + labelWidth + "s .case %-" + primaryKeyWidth + "d%s -> %s", + address, + offset, + "", + primaryKey, + secondaryKeyString, + targetString); + } + } + + /** + * A decoded instruction. + */ + static class Instruction { + Instruction(int offset, Capstone.CsInsn insn, String operandComment, List comments) { + this.offset = offset; + this.insn = insn; + this.operandComment = operandComment; + this.comments = comments; + } + + final int offset; + final Capstone.CsInsn insn; + final String operandComment; + final List comments; + + /** + * The entries of the jump table after this instruction. + */ + final List jumpTable = new ArrayList<>(); + + /** + * The label of this instruction if it is targeted by a jump instruction. Otherwise, + * {@code -1}. + */ + int label = -1; + + /** + * The instruction targeted by this instruction if it is a jump. + */ + Instruction target; + + String labeledAddress() { + return String.format("L%d: 0x%x", label, insn.address); + } + + @Override + public String toString() { + return toString(ONE, ONE, true); + } + + String toString(Width offsetWidth, Width labelWidth, boolean showComments) { + Formatter buf = new Formatter(); + String l = label == -1 ? "" : "L" + label + ":"; + if (showComments && comments != null) { + for (String comment : comments) { + final String commentLinePrefix = ";; "; + buf.format("%s%s%n", commentLinePrefix, comment.replace("\n", "\n" + commentLinePrefix)); + } + } + Object operand = target == null ? insn.opStr : target.labeledAddress(); + buf.format("0x%08x(+0x%-" + offsetWidth + "x): %" + labelWidth + "s %s\t%s%s", insn.address, offset, l, insn.mnemonic, operand, showComments ? operandComment : ""); + Width primaryKeyWidth = new Width(1); + Width secondaryKeyWidth = new Width(1); + for (JumpTableEntry c : jumpTable) { + primaryKeyWidth.update(c.primaryKey, 10); + if (c.secondaryKey != null) { + secondaryKeyWidth.update(c.secondaryKey, 10); + } + } + for (JumpTableEntry c : jumpTable) { + buf.format("\n%s", c.toString(offsetWidth, labelWidth, primaryKeyWidth, secondaryKeyWidth)); + } + return buf.toString(); + } + } + + /** + * Disassembles a given HexCodeFile. + * + * @param out where the HexCodeFile disassembly is printed + */ + public static void process(HexCodeFile hcf, PrintStream out, boolean showComments) { + Capstone cs; + boolean littleEndian; + + switch (hcf.isa.toLowerCase()) { + case "amd64": + littleEndian = true; + cs = new Capstone(Capstone.CS_ARCH_X86, Capstone.CS_MODE_64); + break; + case "aarch64": + littleEndian = true; + cs = new Capstone(Capstone.CS_ARCH_ARM64, Capstone.CS_MODE_ARM); + break; + default: + throw new IllegalArgumentException("Unexpected ISA: " + hcf.isa); + } + + // Ordered map from offset to decoded instructions + Map instructionMap = new LinkedHashMap<>(); + + if (hcf.jumpTables.size() == 0) { + for (Instruction i : disassemble(hcf, cs, 0, hcf.code)) { + instructionMap.put(i.offset, i); + } + } else { + // Break the disassembly into chunks separated by jump tables + int startOffset = 0; + for (JumpTable jumpTable : hcf.jumpTables) { + int jumpTableOffset = jumpTable.getPosition(); + List insns = disassembleAtOffset(hcf, cs, startOffset, jumpTableOffset); + for (Instruction i : insns) { + instructionMap.put(i.offset, i); + } + EntryFormat entryFormat = jumpTable.entryFormat; + int entrySize = entryFormat.size; + Instruction lastInsn = insns.get(insns.size() - 1); + long entries = Math.abs(jumpTable.high - (long) jumpTable.low); + for (int i = 0; i <= entries; i++) { + int entryOffset = jumpTable.getPosition() + i * entrySize; + Integer secondaryKey; + int targetOffsetRelativeToJumpTable; + switch (jumpTable.entryFormat) { + case OFFSET: + secondaryKey = null; + targetOffsetRelativeToJumpTable = readInt(littleEndian, hcf.code, entryOffset); + break; + case KEY2_OFFSET: + secondaryKey = readInt(littleEndian, hcf.code, entryOffset); + targetOffsetRelativeToJumpTable = readInt(littleEndian, hcf.code, entryOffset + 4); + break; + default: + throw new IllegalArgumentException("Unknown jump table entry format: " + jumpTable.entryFormat); + } + int primaryKey = jumpTable.low + i; + long target = hcf.startAddress + jumpTableOffset + targetOffsetRelativeToJumpTable; + lastInsn.jumpTable.add(new JumpTableEntry(entryOffset, hcf.startAddress + entryOffset, primaryKey, secondaryKey, target)); + + } + long newStartOffset = jumpTableOffset + entrySize * (jumpTable.high - (long) jumpTable.low + 1); + startOffset = (int) newStartOffset; + assert startOffset == newStartOffset; + } + if (startOffset < hcf.code.length) { + for (Instruction i : disassembleAtOffset(hcf, cs, startOffset, hcf.code.length)) { + instructionMap.put(i.offset, i); + } + } + } + + Width labelWidth = new Width(1); + Width offsetWidth = new Width(1); + + Instruction[] instructions = instructionMap.values().toArray(new Instruction[0]); + if (instructions.length > 0) { + long firstInsnAddress = instructions[0].insn.address; + long lastInsnAddress = instructions[instructions.length - 1].insn.address; + Map targets = new HashMap<>(); + for (Instruction i : instructions) { + offsetWidth.update(Integer.toHexString(i.offset)); + updateTargets(instructionMap, targets, labelWidth, firstInsnAddress, lastInsnAddress, i); + } + + // Increase max label width to account for "L:" characters. + labelWidth.value += 2; + + for (Instruction i : instructionMap.values()) { + out.println(i.toString(offsetWidth, labelWidth, showComments)); + } + } + out.flush(); + } + + private static void updateTargets(Map instructionMap, + Map targets, + Width labelWidth, + long firstInsnAddress, + long lastInsnAddress, + Instruction i) { + String opStr = i.insn.opStr; + if (opStr != null) { + opStr = opStr.toLowerCase(); + if (opStr.startsWith("0x")) { + opStr = opStr.substring(2); + } + if (opStr.length() != 0) { + char ch = opStr.charAt(0); + if ((ch >= 'a' && ch <= 'f') || (ch >= '0' && ch <= '9')) { + try { + i.target = findTarget(instructionMap, targets, labelWidth, firstInsnAddress, lastInsnAddress, Long.parseLong(opStr, 16)); + } catch (NumberFormatException e) { + } + } + } + } + for (JumpTableEntry e : i.jumpTable) { + e.target = findTarget(instructionMap, targets, labelWidth, firstInsnAddress, lastInsnAddress, (long) e.target); + } + } + + private static Instruction findTarget(Map instructionMap, Map targets, Width labelWidth, long firstInsnAddress, long lastInsnAddress, long address) { + if (address >= firstInsnAddress && address <= lastInsnAddress) { + assert address - firstInsnAddress < Integer.MAX_VALUE; + int offset = (int) (address - firstInsnAddress); + Instruction targetInstruction = instructionMap.get(offset); + if (targetInstruction != null) { + assert targetInstruction.insn.address == address; + int label = targetInstruction.label; + if (label == -1) { + targetInstruction.label = label = targets.size(); + } + labelWidth.update(Integer.toString(label)); + return targets.computeIfAbsent(address, a -> targetInstruction); + } + } + return null; + } + + private static int readInt(boolean littleEndian, byte[] code, int offset) { + if (littleEndian) { + return code[offset + 0] & 0xFF | + (code[offset + 1] & 0xFF) << 8 | + (code[offset + 2] & 0xFF) << 16 | + (code[offset + 3] & 0xFF) << 24; + } + return code[offset + 3] & 0xFF | + (code[offset + 2] & 0xFF) << 8 | + (code[offset + 1] & 0xFF) << 16 | + (code[offset + 0] & 0xFF) << 24; + } + + private static List disassembleAtOffset(HexCodeFile hcf, Capstone cs, int startOffset, int endOffset) { + byte[] section = Arrays.copyOfRange(hcf.code, startOffset, endOffset); + return disassemble(hcf, cs, startOffset, section); + } + + private static List disassemble(HexCodeFile hcf, Capstone cs, int startOffset, byte[] section) { + Capstone.CsInsn[] allInsn = cs.disasm(section, hcf.startAddress + startOffset); + List instructions = new ArrayList<>(allInsn.length); + for (Capstone.CsInsn insn : allInsn) { + int insnOffset = (int) (insn.address - hcf.startAddress); + List comments = hcf.comments.get(insnOffset); + StringBuilder operandComments = null; + for (int i = 0; i < insn.size; i++) { + List list = hcf.operandComments.get(insnOffset + i); + if (list != null) { + for (String c : list) { + if (operandComments == null) { + operandComments = new StringBuilder("\t"); + } else { + operandComments.append(" "); + } + operandComments.append(c); + } + } + } + Instruction instr = new Instruction(insnOffset, insn, operandComments != null ? operandComments.toString() : "", comments); + instructions.add(instr); + } + return instructions; + } + + public static void main(String[] args) throws IOException { + String dirOption = null; + int firstArg = 0; + + for (int i = 0; i < args.length; i++) { + if (args[i].startsWith("-d=")) { + dirOption = args[i].substring(3); + } else if (args[i].startsWith("-")) { + usage(args[i].equals("-h") ? null : "Unexpected option: " + args[i]); + } else { + firstArg = i; + break; + } + } + + File outDir = null; + if (dirOption != null) { + outDir = new File(dirOption); + if (!outDir.isDirectory()) { + if (!outDir.mkdirs()) { + throw new Error("Could not create output directory " + outDir.getAbsolutePath()); + } + } + } + + for (int i = firstArg; i < args.length; i++) { + String arg = args[i]; + Path inputFile = Paths.get(arg); + StringBuilder sb = new StringBuilder(); + try (Stream stream = Files.lines(inputFile)) { + stream.forEach(s -> sb.append(s).append("\n")); + } + String input = sb.toString(); + + String inputSource = inputFile.toAbsolutePath().toString(); + String output = processAll(input, inputSource, EMBEDDED_HCF_OPEN, EMBEDDED_HCF_CLOSE, true); + + Path outputFile; + if (outDir == null) { + outputFile = inputFile; + } else { + outputFile = Paths.get(outDir.toString(), inputFile.toFile().getName()); + } + + if (!outputFile.equals(inputFile) || !output.equals(input)) { + Files.write(outputFile, output.getBytes(StandardCharsets.UTF_8)); + } + } + } + + private static void usage(String s) { + if (s != null) { + System.err.println(s); + } + System.err.println("Usage: hcfdis [ -d=dir ] file [ files ]"); + System.exit(s == null ? 0 : -1); + } +} diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/NativeCodeEditor/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..9be25d9e6494 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.nc +OpenIDE-Module-Layer: at/ssw/visualizer/nc/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/nc/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/Bundle.properties b/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/Bundle.properties new file mode 100644 index 000000000000..8f185ffe92a1 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/Bundle.properties @@ -0,0 +1,10 @@ +OpenIDE-Module-Name=Native Code Editor + +text/x-compilation-nc=Native Code +nc-whitespace=Whitespace +nc-other=Other +nc-address=Address +nc-instruction=Instruction +nc-register=Register +nc-block=Block +nc-comment=Comment diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/icons/collapselir.gif b/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/icons/collapselir.gif new file mode 100644 index 0000000000000000000000000000000000000000..b7e9733e07daa57153d0e2df587219e9e699ebb9 GIT binary patch literal 241 zcmZ?wbhEHb6krfwIKsfd=vLJ|ZS~PpSC5{)diC@FS3m#1`St(Z@Bbfv|Ns2w|F^&Y zfByad>;L~h|Nj5^|Nrm*|Ns7j00YrL@h1x>1A`!g4oC;cP6m!V24xNzj|~eBHnTBn z#GEj2ILN@F;L~h|Nj5^|Nrm*|Ns7j00YrL@h1x>1A`!g4oC;cP6m!*24xNzj|~eBHnTBn z#GEj2ILN@FhkG4kiyczkaUV=!U0tm zRzX*>8=T+J JX{Nwn4FJ@fTXX;b literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/icons/nativecode.gif b/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/icons/nativecode.gif new file mode 100644 index 0000000000000000000000000000000000000000..1854ca7fb364e233c5267cc358c5010612091f65 GIT binary patch literal 91 zcmZ?wbhEHb6krfwSjfPjF0u9h|NlS|h!lUaFfuSOG3YP=0Z5*KNoPv`%F}Q8g#|V@ qX*pk&VR@1p{zGx;2UabvAn~Pp6Wpsm--um4aot}zPA+~{25SJu2OTm1 literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/layer.xml b/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/layer.xml new file mode 100644 index 000000000000..80b080ee8cf9 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/layer.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/model/NetBeans-NC-fontsColors.xml b/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/model/NetBeans-NC-fontsColors.xml new file mode 100644 index 000000000000..42c5b3f353a4 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/model/NetBeans-NC-fontsColors.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/NativeCodeView/pom.xml b/visualizer/C1Visualizer/NativeCodeView/pom.xml new file mode 100644 index 000000000000..6707b348db74 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeView/pom.xml @@ -0,0 +1,102 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + NativeCodeView + 1.13-SNAPSHOT + nbm + NativeCodeView + + UTF-8 + + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + at.ssw.visualizer + NativeCodeEditor + ${project.version} + + + at.ssw.visualizer + TextEditor + ${project.version} + + + org.netbeans.api + org-netbeans-modules-editor + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-lib + ${netbeans.version} + + + org.netbeans.api + org-openide-awt + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.NativeCodeView + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/NativeCodeView/src/main/java/at/ssw/visualizer/nc/view/NCViewAction.java b/visualizer/C1Visualizer/NativeCodeView/src/main/java/at/ssw/visualizer/nc/view/NCViewAction.java new file mode 100644 index 000000000000..4a90ee7950ec --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeView/src/main/java/at/ssw/visualizer/nc/view/NCViewAction.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.nc.view; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.openide.util.NbBundle; +import org.openide.windows.TopComponent; + +/** + * + * @author Alexander Reder + */ +public class NCViewAction extends AbstractAction { + + public NCViewAction() { + super(NbBundle.getMessage(NCViewAction.class, "CTL_NCViewAction")); + } + + public void actionPerformed(ActionEvent evt) { + TopComponent win = NCViewTopComponent.findInstance(); + win.open(); + win.requestActive(); + } +} diff --git a/visualizer/C1Visualizer/NativeCodeView/src/main/java/at/ssw/visualizer/nc/view/NCViewTopComponent.java b/visualizer/C1Visualizer/NativeCodeView/src/main/java/at/ssw/visualizer/nc/view/NCViewTopComponent.java new file mode 100644 index 000000000000..03e75e3a8765 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeView/src/main/java/at/ssw/visualizer/nc/view/NCViewTopComponent.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.nc.view; + +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.nc.NCEditorKit; +import at.ssw.visualizer.nc.model.NCTextBuilder; +import at.ssw.visualizer.texteditor.view.AbstractTextViewTopComponent; +import java.io.Serializable; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; + +/** + * + * @author Alexander Reder + */ +final class NCViewTopComponent extends AbstractTextViewTopComponent { + + private static NCViewTopComponent instance; + private static final String PREFERRED_ID = "NCViewTopComponent"; + + private NCViewTopComponent() { + super(new NCEditorKit()); + setName("Native Code"); + setToolTipText("Native Code"); + + } + + @Override + protected String getContent(ControlFlowGraph cfg, BasicBlock[] blocks) { + NCTextBuilder builder = new NCTextBuilder(); + return builder.buildView(cfg, blocks); + } + + // + public static synchronized NCViewTopComponent getDefault() { + if (instance == null) { + instance = new NCViewTopComponent(); + } + return instance; + } + + public static synchronized NCViewTopComponent findInstance() { + return (NCViewTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID); + } + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_ALWAYS; + } + + @Override + public void componentOpened() { + // TODO add custom code on component opening + } + + @Override + public void componentClosed() { + // TODO add custom code on component closing + } + + /** replaces this in object stream */ + @Override + public Object writeReplace() { + return new ResolvableHelper(); + } + + @Override + protected String preferredID() { + return PREFERRED_ID; + } + + final static class ResolvableHelper implements Serializable { + + private static final long serialVersionUID = 1L; + + public Object readResolve() { + return NCViewTopComponent.getDefault(); + } + } + // + +} diff --git a/visualizer/C1Visualizer/NativeCodeView/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/NativeCodeView/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..ffbe37e2ec47 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeView/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.nc.view +OpenIDE-Module-Layer: at/ssw/visualizer/nc/view/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/nc/view/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/Bundle.properties b/visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/Bundle.properties new file mode 100644 index 000000000000..c3c0cf25b76e --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/Bundle.properties @@ -0,0 +1,4 @@ +CTL_NCViewAction=NCView +CTL_NCViewTopComponent=NCView Window +HINT_NCViewTopComponent=This is a NCView window +OpenIDE-Module-Name=Native Code View diff --git a/visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/NCViewTopComponentSettings.xml b/visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/NCViewTopComponentSettings.xml new file mode 100644 index 000000000000..9bd4eba5524f --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/NCViewTopComponentSettings.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/NCViewTopComponentWstcref.xml b/visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/NCViewTopComponentWstcref.xml new file mode 100644 index 000000000000..27564a7c4dd4 --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/NCViewTopComponentWstcref.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/layer.xml b/visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/layer.xml new file mode 100644 index 000000000000..4ea3297c74df --- /dev/null +++ b/visualizer/C1Visualizer/NativeCodeView/src/main/resources/at/ssw/visualizer/nc/view/layer.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/TextEditor/pom.xml b/visualizer/C1Visualizer/TextEditor/pom.xml new file mode 100644 index 000000000000..c895716e78a5 --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/pom.xml @@ -0,0 +1,139 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + TextEditor + 1.13-SNAPSHOT + nbm + TextEditor + + UTF-8 + + + + at.ssw.visualizer + VisualizerUI + ${project.version} + + + at.ssw.visualizer + CompilationModel + ${project.version} + + + org.netbeans.api + org-netbeans-modules-editor + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-fold + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-lib + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-lib2 + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-mimelookup + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-settings + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-editor-util + ${netbeans.version} + + + org.netbeans.api + org-openide-actions + ${netbeans.version} + + + org.netbeans.api + org-openide-awt + ${netbeans.version} + + + org.netbeans.api + org-openide-nodes + ${netbeans.version} + + + org.netbeans.api + org-openide-text + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.texteditor + at.ssw.visualizer.texteditor.model + at.ssw.visualizer.texteditor.view + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/Editor.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/Editor.java new file mode 100644 index 000000000000..c8d60379c04b --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/Editor.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor; + +import at.ssw.visualizer.core.selection.Selection; +import at.ssw.visualizer.core.selection.SelectionManager; +import at.ssw.visualizer.core.selection.SelectionProvider; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.texteditor.model.BlockRegion; +import at.ssw.visualizer.texteditor.model.Text; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.openide.text.CloneableEditor; +import org.openide.windows.TopComponent; + +/** + * Abstract template class of a Editor class of the Visualizer. + * + * Must be initialized with a custom EditorSupport class and the + * method creatClonedObject must be overwritten by + * the Editor implementation. + * + * @author Alexander Reder + */ +public abstract class Editor extends CloneableEditor implements SelectionProvider { + + protected Selection selection; + private boolean selectionUpdating; + private BasicBlock[] curBlocks; + private boolean initialized; + + protected Editor(EditorSupport support) { + super(support); + selection = new Selection(); + selection.put(support.getControlFlowGraph()); + selection.addChangeListener(selectionListener); + } + + public Selection getSelection() { + return selection; + } + + @Override + protected void componentShowing() { + super.componentShowing(); + if (!initialized) { + getEditorPane().addCaretListener(caretListener); + initialized = true; + } + } + + @Override + protected void componentActivated() { + super.componentActivated(); + SelectionManager.getDefault().setSelection(selection); + } + + @Override + protected void componentClosed() { + super.componentClosed(); + SelectionManager.getDefault().removeSelection(selection); + } + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_NEVER; + } + + private ChangeListener selectionListener = new ChangeListener() { + public void stateChanged(ChangeEvent event) { + if (selectionUpdating) { + return; + } + selectionUpdating = true; + + Text text = (Text) getEditorPane().getDocument().getProperty(Text.class); + BasicBlock[] newBlocks = selection.get(BasicBlock[].class); + + if (newBlocks != null && newBlocks.length > 0 && !Arrays.equals(curBlocks, newBlocks)) { + BlockRegion r = text.getBlocks().get(newBlocks[0]); + int startOffset = r.getNameStart(); + int endOffset = r.getNameEnd(); + + if (newBlocks.length > 1) { + for (BasicBlock b : newBlocks) { + r = text.getBlocks().get(b); + startOffset = Math.min(startOffset, r.getStart()); + endOffset = Math.max(endOffset, r.getEnd()); + } + } + + getEditorPane().select(startOffset, endOffset); + } + curBlocks = newBlocks; + selectionUpdating = false; + } + }; + + + private CaretListener caretListener = new CaretListener() { + public void caretUpdate(CaretEvent event) { + if (selectionUpdating) { + return; + } + selectionUpdating = true; + + Text text = (Text) getEditorPane().getDocument().getProperty(Text.class); + List newBlocks = new ArrayList(); + int startOffset = Math.min(event.getDot(), event.getMark()); + int endOffset = Math.max(event.getDot(), event.getMark()); + + for (BlockRegion region : text.getBlocks().values()) { + if (region.getStart() <= endOffset && region.getEnd() > startOffset) { + newBlocks.add(region.getBlock()); + } + } + + curBlocks = newBlocks.toArray(new BasicBlock[newBlocks.size()]); + selection.put(curBlocks); + selectionUpdating = false; + } + }; + +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/EditorKit.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/EditorKit.java new file mode 100644 index 000000000000..b867ce8987c6 --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/EditorKit.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor; + +import at.ssw.visualizer.texteditor.tooltip.ToolTipAction; +import javax.swing.Action; +import javax.swing.text.TextAction; +import org.netbeans.modules.editor.NbEditorKit; + +/** + * Abstract template class of a EditorKit class of the Visualizer. + * + * The scanner field must be initialized with the + * custom Scanner implementation and the method + * getContentType must be overwritten and return the mime type + * for the editor. + * + * @author Alexander Reder + */ +public abstract class EditorKit extends NbEditorKit { + + @Override + protected Action[] getCustomActions() { + Action[] prev = super.getCustomActions(); + Action[] added = new Action[]{new ToolTipAction()}; + if (prev != null) { + return TextAction.augmentList(prev, added); + } else { + return added; + } + } + +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/EditorSupport.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/EditorSupport.java new file mode 100644 index 000000000000..2fb92441802e --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/EditorSupport.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor; + +import at.ssw.visualizer.model.Compilation; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.texteditor.model.Text; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.beans.VetoableChangeListener; +import java.beans.VetoableChangeSupport; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Date; +import javax.swing.text.EditorKit; +import javax.swing.text.StyledDocument; +import org.openide.cookies.EditorCookie; +import org.openide.cookies.EditCookie; +import org.openide.text.CloneableEditorSupport; +import org.openide.windows.CloneableOpenSupport; + +/** + * Abstract template class of a EditorSupport class of the + * Visulizer. + * + * The text field must be initialized by the implementing class + * and the methods createCloneableEditor and + * initializeCloneableEditor must be overwritten by the implenting class. + * createCloneableEditor must return a custom implementation + * of the Editor class. + * initializeClonableEditor is used to set the icon, e.g. + * editor.setIcon(Utilities.loadImage(IconsImage)); . + * + * @author Bernhard Stiftner + * @author Christian Wimmer + * @author Alexander Reder + */ +public abstract class EditorSupport extends CloneableEditorSupport implements EditCookie, EditorCookie, EditorCookie.Observable { + + protected ControlFlowGraph cfg; + protected Text text; + + protected EditorSupport(ControlFlowGraph cfg) { + super(new Env()); + ((Env) this.env).editorSupport = this; + this.cfg = cfg; + } + + public ControlFlowGraph getControlFlowGraph() { + return cfg; + } + + @Override + protected StyledDocument createStyledDocument(EditorKit kit) { + StyledDocument doc = super.createStyledDocument(kit); + + // Back-link from Document to our internal data model. + doc.putProperty(Text.class, text); + doc.putProperty(Compilation.class, cfg.getCompilation()); + doc.putProperty(ControlFlowGraph.class, cfg); + + return doc; + } + + public abstract String getMimeType(); + + protected String messageOpening() { + return "Opening " + messageToolTip(); + } + + protected String messageOpened() { + return "Opened " + messageToolTip(); + } + + protected String messageSave() { + throw new UnsupportedOperationException("Not supported yet."); + } + + protected String messageName() { + return cfg.getCompilation().getShortName(); + } + + protected String messageToolTip() { + return cfg.getCompilation().getMethod() + " - " + cfg.getName(); + } + + public static class Env implements CloneableEditorSupport.Env { + + private PropertyChangeSupport prop = new PropertyChangeSupport(this); + private VetoableChangeSupport veto = new VetoableChangeSupport(this); + + /** + * Back-link to outer class EditorSupport. Env must be a static class + * because it is passed to super constructor of EditorSupport. + */ + private EditorSupport editorSupport; + + public InputStream inputStream() throws IOException { + return new ByteArrayInputStream(editorSupport.text.getText().getBytes()); + } + + public OutputStream outputStream() throws IOException { + throw new IOException("Editor is readonly"); + } + + public Date getTime() { + return editorSupport.cfg.getCompilation().getDate(); + } + + public String getMimeType() { + return editorSupport.getMimeType(); + } + + public boolean isValid() { + return true; + } + + public boolean isModified() { + return false; + } + + public void markModified() throws IOException { + throw new IOException("Editor is readonly"); + } + + public void unmarkModified() { + // Nothing to do. + } + + public CloneableOpenSupport findCloneableOpenSupport() { + return editorSupport; + } + + public void addPropertyChangeListener(PropertyChangeListener l) { + prop.addPropertyChangeListener(l); + } + + public void removePropertyChangeListener(PropertyChangeListener l) { + prop.removePropertyChangeListener(l); + } + + public void addVetoableChangeListener(VetoableChangeListener l) { + veto.addVetoableChangeListener(l); + } + + public void removeVetoableChangeListener(VetoableChangeListener l) { + veto.removeVetoableChangeListener(l); + } + } + +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/fold/FoldManager.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/fold/FoldManager.java new file mode 100644 index 000000000000..e8b488aa151f --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/fold/FoldManager.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor.fold; + +import at.ssw.visualizer.texteditor.model.FoldingRegion; +import at.ssw.visualizer.texteditor.model.Text; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JComponent; +import javax.swing.event.DocumentEvent; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import org.netbeans.api.editor.fold.Fold; +import org.netbeans.editor.CodeFoldingSideBar; +import org.netbeans.spi.editor.fold.FoldHierarchyTransaction; +import org.netbeans.spi.editor.fold.FoldOperation; + +/** + * + * @author Alexander Reder + */ +public class FoldManager implements org.netbeans.spi.editor.fold.FoldManager { + + protected FoldOperation operation; + + public void init(FoldOperation operation) { + this.operation = operation; + } + + public void initFolds(FoldHierarchyTransaction transaction) { + Document document = operation.getHierarchy().getComponent().getDocument(); + Text text = (Text) document.getProperty(Text.class); + if (document.getLength() == 0 || text == null) { + return; + } + + try { + for (FoldingRegion fr : text.getFoldings()) { + operation.addToHierarchy(fr.getKind(), fr.getKind().toString(), fr.isInitiallyCollapsed(), fr.getStart(), fr.getEnd(), 0, 0, null, transaction); + } + } catch (BadLocationException ex) { + Logger logger = Logger.getLogger(FoldManager.class.getName()); + logger.log(Level.SEVERE, ex.getMessage(), ex); + } + } + + public void insertUpdate(DocumentEvent arg0, FoldHierarchyTransaction arg1) { + } + + public void removeUpdate(DocumentEvent arg0, FoldHierarchyTransaction arg1) { + } + + public void changedUpdate(DocumentEvent arg0, FoldHierarchyTransaction arg1) { + } + + public void removeEmptyNotify(Fold arg0) { + } + + public void removeDamagedNotify(Fold arg0) { + } + + public void expandNotify(Fold arg0) { + } + + public void release() { + } + + public static class FoldManagerFactory implements org.netbeans.spi.editor.fold.FoldManagerFactory { + + public FoldManager createFoldManager() { + return new FoldManager(); + } + } + + public static class SideBarFactory implements org.netbeans.editor.SideBarFactory { + + public JComponent createSideBar(JTextComponent target) { + return new CodeFoldingSideBar(target); + } + } + +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/highlight/HighlightsContainer.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/highlight/HighlightsContainer.java new file mode 100644 index 000000000000..78c3605261b4 --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/highlight/HighlightsContainer.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor.highlight; + +import at.ssw.visualizer.texteditor.model.Scanner; +import at.ssw.visualizer.texteditor.model.Text; +import at.ssw.visualizer.texteditor.model.TextRegion; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.text.AttributeSet; +import javax.swing.text.Caret; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import javax.swing.text.SimpleAttributeSet; +import org.netbeans.api.editor.mimelookup.MimeLookup; +import org.netbeans.api.editor.mimelookup.MimePath; +import org.netbeans.api.editor.settings.FontColorSettings; +import org.netbeans.editor.TokenID; +import org.netbeans.spi.editor.highlighting.HighlightsSequence; +import org.netbeans.spi.editor.highlighting.ZOrder; +import org.netbeans.spi.editor.highlighting.support.AbstractHighlightsContainer; +import org.openide.util.WeakListeners; + +/** + * + * @author Christian Wimmer + * @author Alexander Reder + */ +public class HighlightsContainer extends AbstractHighlightsContainer { + + private static final String HIGHLIGHT_COLORING = "at-ssw-visualizer-highlight"; + + protected final JTextComponent component; + protected final Document document; + protected final AttributeSet highlightColoring; + protected final Scanner scanner; + + protected TextRegion[] curRegions = null; + + private final CaretListener caretListener = new CaretListener() { + + public void caretUpdate(CaretEvent event) { + TextRegion[] newRegions = findRegions(); + if (newRegions != curRegions) { + curRegions = newRegions; + fireHighlightsChange(0, document.getLength()); + } + } + + }; + + protected HighlightsContainer(JTextComponent component, Document document) { + this.document = document; + this.component = component; + component.addCaretListener(WeakListeners.create(CaretListener.class, caretListener, component)); + + // Load the coloring. + Text t = (Text) document.getProperty(Text.class); + MimePath mimePath = MimePath.parse(t.getMimeType()); + FontColorSettings fcs = MimeLookup.getLookup(mimePath).lookup(FontColorSettings.class); + AttributeSet highlight = fcs.getFontColors(HIGHLIGHT_COLORING); + highlightColoring = highlight != null ? highlight : SimpleAttributeSet.EMPTY; + + scanner = t.getScanner(); + scanner.setText(document); + curRegions = findRegions(); + } + + public HighlightsSequence getHighlights(int startOffset, int endOffset) { + return new RegionSequence(); + } + + protected TextRegion[] findRegions() { + Text text = (Text) document.getProperty(Text.class); + Caret caret = component.getCaret(); + if (text == null || caret == null) { + return null; + } + scanner.findTokenBegin(caret.getDot()); + TokenID token = scanner.nextToken(); + if (token.getNumericID() < 0) { + return null; + } + + return text.getHighlighting(scanner.getTokenString()); + } + + protected class RegionSequence implements HighlightsSequence { + + private int idx = -1; + + public boolean moveNext() { + idx++; + return curRegions != null && idx < curRegions.length; + } + + public int getStartOffset() { + return curRegions[idx].getStart(); + } + + public int getEndOffset() { + return curRegions[idx].getEnd(); + } + + public AttributeSet getAttributes() { + return highlightColoring; + } + } + + public static final class HighlightsLayerFactory implements org.netbeans.spi.editor.highlighting.HighlightsLayerFactory { + + public org.netbeans.spi.editor.highlighting.HighlightsLayer[] createLayers(Context context) { + Text t = (Text) context.getDocument().getProperty(Text.class); + if(t == null) { + return new org.netbeans.spi.editor.highlighting.HighlightsLayer[0]; + } + return new org.netbeans.spi.editor.highlighting.HighlightsLayer[]{org.netbeans.spi.editor.highlighting.HighlightsLayer.create("at-ssw-visualizer-highlighting", ZOrder.SHOW_OFF_RACK, true, new HighlightsContainer(context.getComponent(), context.getDocument()))}; + } + + } +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/hyperlink/HyperlinkProvider.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/hyperlink/HyperlinkProvider.java new file mode 100644 index 000000000000..d04184e85e80 --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/hyperlink/HyperlinkProvider.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor.hyperlink; + +import at.ssw.visualizer.texteditor.model.Scanner; +import at.ssw.visualizer.texteditor.model.Text; +import at.ssw.visualizer.texteditor.model.TextRegion; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import org.netbeans.editor.TokenID; +import org.netbeans.editor.Utilities; + +/** + * + * @author Bernhard Stiftner + * @author Christian Wimmer + * @author Alexander Reder + */ +public class HyperlinkProvider implements org.netbeans.lib.editor.hyperlink.spi.HyperlinkProvider { + + protected Scanner scanner = null; + + protected TextRegion findTarget(Document doc, int offset) { + Text text = (Text) doc.getProperty(Text.class); + if (text == null) { + return null; + } + + scanner = text.getScanner(); + scanner.setText(doc); + scanner.findTokenBegin(offset); + TokenID token = scanner.nextToken(); + if (token.getNumericID() < 0) { + return null; + } + + return text.getHyperlinkTarget(scanner.getTokenString()); + } + + public boolean isHyperlinkPoint(Document doc, int offset) { + return findTarget(doc, offset) != null; + } + + public int[] getHyperlinkSpan(Document doc, int offset) { + if (findTarget(doc, offset) != null) { + return new int[]{scanner.getTokenOffset(), scanner.getTokenOffset() + scanner.getTokenLength()}; + } + return null; + } + + public void performClickAction(Document doc, int offset) { + TextRegion target = findTarget(doc, offset); + if (target != null) { + JTextComponent editor = Utilities.getFocusedComponent(); + editor.select(target.getStart(), target.getEnd()); + } + } + +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/BlockRegion.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/BlockRegion.java new file mode 100644 index 000000000000..b61922553ea3 --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/BlockRegion.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor.model; + +import at.ssw.visualizer.model.cfg.BasicBlock; + +/** + * + * @author Christian Wimmer + */ +public class BlockRegion extends TextRegion { + + private BasicBlock block; + + private int nameStart; + private int nameEnd; + + public BlockRegion(BasicBlock block, int start, int end, int nameStart, int nameEnd) { + super(start, end); + this.block = block; + this.nameStart = nameStart; + this.nameEnd = nameEnd; + } + + + public BasicBlock getBlock() { + return block; + } + + public int getNameStart() { + return nameStart; + } + + public int getNameEnd() { + return nameEnd; + } +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/FoldingRegion.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/FoldingRegion.java new file mode 100644 index 000000000000..838e7484916e --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/FoldingRegion.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor.model; + +import org.netbeans.api.editor.fold.FoldType; + +/** + * + * @author Christian Wimmer + * @author Alexander Reder + */ +public class FoldingRegion extends TextRegion { + + + private FoldType kind; + private boolean initallyCollapsed; + + + public FoldingRegion(FoldType kind, int start, int end, boolean initiallyCollapsed) { + super(start, end); + this.kind = kind; + this.initallyCollapsed = initiallyCollapsed; + } + + + public FoldType getKind() { + return kind; + } + + public boolean isInitiallyCollapsed() { + return initallyCollapsed; + } + +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/HoverParser.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/HoverParser.java new file mode 100644 index 000000000000..ff6cb0be7bdc --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/HoverParser.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor.model; + +import java.util.Iterator; + +/** + * + * @author ChristianWimmer + */ +public class HoverParser implements Iterator { + + private static String HOVER_START = "<@"; + private static String HOVER_SEP = "|@"; + private static String HOVER_END = ">@"; + private String text; + private int curPos; + private String curText; + private String curHover; + private boolean curNewLine; + + public static String firstLine(String text) { + if (text == null) { + return ""; + } + HoverParser p = new HoverParser(text); + StringBuilder result = new StringBuilder(text.length()); + while (p.hasNext()) { + String part = p.next(); + if (p.isNewLine()) { + break; + } + result.append(part); + } + return result.toString(); + } + + public HoverParser(String text) { + this.text = text; + } + + private void advance() { + int lineStart = text.indexOf('\n', curPos); + int nextStart = text.indexOf(HOVER_START, curPos); + + if (lineStart == curPos) { + curText = "\n"; + curHover = null; + curPos = lineStart + 1; + curNewLine = true; + while (curPos < text.length() && text.charAt(curPos) <= ' ') { + curPos++; + } + return; + } + curNewLine = false; + if (lineStart != -1 && (nextStart == -1 || lineStart < nextStart)) { + curText = text.substring(curPos, lineStart); + curHover = null; + curPos = lineStart; + return; + } + + if (nextStart == curPos) { + int nextSep = text.indexOf(HOVER_SEP, nextStart); + if (nextSep != -1) { + int nextEnd = text.indexOf(HOVER_END, nextSep); + if (nextEnd != -1) { + curText = text.substring(nextStart + HOVER_START.length(), nextSep); + curHover = text.substring(nextSep + HOVER_SEP.length(), nextEnd); + while (curHover.endsWith("\n")) { + curHover = curHover.substring(0, curHover.length() - 1); + } + curPos = nextEnd + HOVER_END.length(); + return; + } + } + } + + if (nextStart == curPos) { + // Incomplete hover sequence. Make sure we make progress by just advancing to the next chararter. + nextStart++; + } + + if (nextStart != -1) { + curText = text.substring(curPos, nextStart); + curHover = null; + curPos = nextStart; + } else if (curPos < text.length()) { + curText = text.substring(curPos); + curHover = null; + curPos = text.length(); + } else { + curText = null; + curHover = null; + } + } + + public boolean hasNext() { + return curPos < text.length(); + } + + public String next() { + advance(); + return curText; + } + + public String getHover() { + return curHover; + } + + public boolean isNewLine() { + return curNewLine; + } + + public void remove() { + throw new UnsupportedOperationException("Not supported."); + } +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/Scanner.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/Scanner.java new file mode 100644 index 000000000000..9d7a988198a6 --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/Scanner.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor.model; + +import java.util.BitSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.netbeans.editor.Syntax; +import org.netbeans.editor.TokenContextPath; + +/** + * The implementing class must specify the used TokenIDs and + * implement the scanner for the specified text. + * + * @author Alexander Reder + * @author Christian Wimmer + */ +public abstract class Scanner extends Syntax { + protected static final int EOF = 0; + protected static final BitSet DIGIT = charsOf("0123456789"); + protected static final BitSet HEX = charsOf("0123456789abcdefABCDEF"); + protected static final BitSet LETTER = charsOf("_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + protected static final BitSet LETTER_DIGIT = charsOf("0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + protected static final BitSet LC_LETTER = charsOf("_abcdefghijklmnopqrstuvwxyz"); + protected static final BitSet LC_LETTER_DIGIT = charsOf("0123456789_abcdefghijklmnopqrstuvwxyz"); + protected static final BitSet REFERENCE_CHARS = charsOf("*. "); + + protected static BitSet charsOf(String s) { + BitSet result = new BitSet(); + for (int i = 0; i < s.length(); i++) { + result.set(s.charAt(i)); + } + return result; + } + + protected BitSet whitespace; + protected char ch; + + public Scanner(String whitespace, TokenContextPath tokenContextPath) { + this.whitespace = charsOf(whitespace); + this.whitespace.set(EOF); + + this.tokenContextPath = tokenContextPath; + } + + public void setText(Document document) { + try { + setText(document.getText(0, document.getLength()), 0, document.getLength()); + } catch (BadLocationException ex) { + Logger logger = Logger.getLogger(Scanner.class.getName()); + logger.log(Level.SEVERE, ex.getMessage(), ex); + } + } + + public void setText(String s, int offset, int length) { + this.buffer = s.toCharArray(); + this.offset = offset; + this.tokenOffset = offset; + this.stopOffset = Math.min(buffer.length, offset + length); + } + + public String getTokenString() { + return new String(buffer, getTokenOffset(), getTokenLength()); + } + + public void findTokenBegin(int offset) { + this.offset = Math.max(offset, 0); + findTokenBegin(); + } + + /** + * If offset is in a token this method will read backwards until a + * whitespace character occurs. + */ + protected void findTokenBegin() { + if (offset >= stopOffset) { + offset = stopOffset - 1; + } + + if (!whitespace.get(buffer[offset])) { + while (offset > 0 && !whitespace.get(buffer[offset - 1])) { + offset--; + } + } + ch = buffer[offset]; + tokenOffset = offset; + } + + /** + * Reads the next character. + */ + protected void readNext() { + offset++; + if (offset < stopOffset) { + ch = buffer[offset]; + } else { + ch = EOF; + } + } + + protected boolean isWhitespace() { + boolean result = false; + while (whitespace.get(ch) && ch != EOF) { + result = true; + readNext(); + } + return result; + } + + /** + * Read to the next whitespace + */ + protected void readToWhitespace() { + do { + readNext(); + } while (!whitespace.get(ch)); + } + + private boolean readNextOrRestart(boolean result, boolean readNext) { + if (result) { + if (readNext) { + readNext(); + } + } else { + offset = tokenOffset; + ch = buffer[offset]; + } + return result; + } + + protected boolean isKeyword(Set keywords) { + int beginOffset = offset; + int endOffset = offset; + while (endOffset < stopOffset && !whitespace.get(buffer[endOffset])) { + endOffset++; + } + String word = new String(buffer, beginOffset, endOffset - beginOffset); + if (!keywords.contains(word)) { + return false; + } + + offset = endOffset - 1; + readNext(); + return true; + } + + protected boolean expectEnd() { + return readNextOrRestart(whitespace.get(ch), false); + } + + protected boolean expectEnd(char expected) { + return readNextOrRestart(ch == expected, false) && offset + 1 < stopOffset && whitespace.get(buffer[offset + 1]); + } + + protected boolean expectChar(char expected) { + return readNextOrRestart(ch == expected, true); + } + + protected boolean expectChar(BitSet expected) { + return readNextOrRestart(expected.get(ch), true); + } + + protected boolean expectChars(BitSet expected) { + while (expected.get(ch)) { + readNext(); + } + return true; + } + + protected boolean skipUntil(char expected) { + while (ch != expected && !whitespace.get(ch)) { + readNext(); + } + return expectChar(expected); + } + + protected boolean beforeChar(char before) { + int curOffset = offset - 1; + while (curOffset >= 0 && buffer[curOffset] != before && whitespace.get(buffer[curOffset])) { + curOffset--; + } + return curOffset >= 0 && buffer[curOffset] == before; + } + + protected boolean beforeChars(BitSet before) { + int curOffset = offset - 1; + while (curOffset >= 0 && !before.get(buffer[curOffset]) && whitespace.get(buffer[curOffset])) { + curOffset--; + } + return curOffset >= 0 && before.get(buffer[curOffset]); + } +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/Text.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/Text.java new file mode 100644 index 000000000000..364bf2fc94fc --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/Text.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor.model; + +import at.ssw.visualizer.model.Compilation; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import java.util.Map; + +/** + * @author Christian Wimmer + * @author Alexander Reder + */ +public class Text { + + private Compilation compilation; + private ControlFlowGraph cfg; + + private String text; + private FoldingRegion[] foldings; + private Map hyperlinks; + private Map stringHovers; + private Map regionHovers; + private Map highlighting; + private Map blocks; + private Scanner scanner; + private String mimeType; + + + public Text(ControlFlowGraph cfg, String text, FoldingRegion[] foldings, Map hyperlinks, Map stringHovers, Map regionHovers, Map highlighting, Map blocks, Scanner scanner, String mimeType) { + this.compilation = cfg.getCompilation(); + this.cfg = cfg; + this.text = text; + this.foldings = foldings; + this.hyperlinks = hyperlinks; + this.stringHovers = stringHovers; + this.regionHovers = regionHovers; + this.highlighting = highlighting; + this.blocks = blocks; + this.scanner = scanner; + this.mimeType = mimeType; + } + + + public Compilation getCompilation() { + return compilation; + } + + public ControlFlowGraph getCfg() { + return cfg; + } + + public String getText() { + return text; + } + + public FoldingRegion[] getFoldings() { + return foldings; + } + + public TextRegion getHyperlinkTarget(String key) { + return hyperlinks.get(key); + } + + public String getStringHover(String key) { + return stringHovers.get(key); + } + + public String getRegionHover(int position) { + for (TextRegion r : regionHovers.keySet()) { + if (r.getStart() <= position && r.getEnd() >= position) { + return regionHovers.get(r); + } + } + return null; + } + + public TextRegion[] getHighlighting(String key) { + return highlighting.get(key); + } + + public Map getBlocks() { + return blocks; + } + + public Scanner getScanner() { + return scanner; + } + + public String getMimeType() { + return mimeType; + } +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/TextBuilder.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/TextBuilder.java new file mode 100644 index 000000000000..99fb03c3d34c --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/TextBuilder.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor.model; + +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * + * @author Alexander Reder + */ +public abstract class TextBuilder { + + protected StringBuilder text; + protected Scanner scanner; + protected List foldingRegions; + protected Map hyperlinks; + protected Map stringHovers; + protected Map regionHovers; + protected Map highlighting; + protected Map blocks; + protected Set hoverKeys; + protected Map hoverDefinitions; + protected Map> hoverReferences; + + public TextBuilder() { + text = new StringBuilder(4 * 1024); + foldingRegions = new ArrayList(); + hyperlinks = new HashMap(); + stringHovers = new HashMap(); + regionHovers = new HashMap(); + highlighting = new HashMap(); + blocks = new HashMap(); + hoverKeys = new HashSet(); + hoverDefinitions = new HashMap(); + hoverReferences = new HashMap>(); + } + + public abstract Text buildDocument(ControlFlowGraph cfg); + + protected abstract void buildHighlighting(); + + protected Text buildText(ControlFlowGraph cfg, String mimeType) { + buildHovers(); + buildHighlighting(); + return new Text(cfg, text.toString(), foldingRegions.toArray(new FoldingRegion[foldingRegions.size()]), hyperlinks, stringHovers, regionHovers, highlighting, blocks, scanner, mimeType); + } + + protected void appendBlockDetails(BasicBlock block) { + text.append(blockDetails(block)); + } + + protected String blockDetails(BasicBlock block) { + StringBuilder sb = new StringBuilder(); + sb.append(block.getName()); + hoverKeys.add(block.getName()); + appendBlockList(sb, " <- ", block.getPredecessors()); + appendBlockList(sb, " -> ", block.getSuccessors()); + appendBlockList(sb, " xh ", block.getXhandlers()); + if (block.getDominator() != null) { + sb.append(" dom ").append(block.getDominator().getName()); + } + sb.append(" [").append(block.getFromBci()).append(", ").append(block.getToBci()).append("]"); + appendList(sb, " ", block.getFlags()); + + if (block.getLoopDepth() > 0) { + sb.append(" (loop ").append(block.getLoopIndex()).append(" depth ").append(block.getLoopDepth()).append(")"); + } + return sb.toString(); + } + + protected void appendBlockList(StringBuilder sb, String prefix, List blocks) { + for (BasicBlock block : blocks) { + sb.append(prefix); + prefix = ","; + sb.append(block.getName()); + } + } + + private void appendList(StringBuilder sb, String prefix, List values) { + for (String value : values) { + sb.append(prefix).append(value); + prefix = ","; + } + } + + protected void buildHovers() { + StringBuilder sb; + for(String key : hoverKeys) { + sb = new StringBuilder(); + if(hoverDefinitions.containsKey(key) && hoverReferences.containsKey(key)) { + sb.append("Definition;\n"); + sb.append(hoverDefinitions.get(key)); + sb.append("\n"); + } + if(hoverReferences.containsKey(key)) { + sb.append("References:\n"); + for(String ref : hoverReferences.get(key)) { + sb.append(ref); + sb.append("\n"); + } + } + if(sb.length() > 0) { + stringHovers.put(key, sb.toString().substring(0, sb.length() - 1)); + } + } + } + +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/TextRegion.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/TextRegion.java new file mode 100644 index 000000000000..266c75dc030d --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/model/TextRegion.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor.model; + +/** + * + * @author Christian Wimmer + * @author Alexander Reder + */ +public class TextRegion { + + private int start; + private int end; + + public TextRegion(int start, int end) { + this.start = start; + this.end = end; + } + + + public int getStart() { + return start; + } + + public int getEnd() { + return end; + } + +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/tooltip/StyledToolTip.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/tooltip/StyledToolTip.java new file mode 100644 index 000000000000..e2c6dee30c35 --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/tooltip/StyledToolTip.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor.tooltip; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Graphics; +import javax.swing.JEditorPane; +import javax.swing.JPanel; +import javax.swing.border.LineBorder; +import javax.swing.text.EditorKit; + +/** + * + * @author Bernhard Stiftner + * @author Christian Wimmer + * @author Alexander Reder + */ +public class StyledToolTip extends JPanel { + + private final String text; + private final JEditorPane toolTipPane; + + public StyledToolTip(String text, EditorKit editorKit) { + this.text = text; + + setBackground(new Color(235, 235, 163)); + setBorder(new LineBorder(Color.BLACK)); + setOpaque(true); + + toolTipPane = new JEditorPane(); + toolTipPane.putClientProperty("document-view-accurate-span", true); + toolTipPane.setEditorKit(editorKit); + toolTipPane.setText(text); + + setLayout(new BorderLayout()); + add(toolTipPane, BorderLayout.CENTER); + } + + @Override + protected void paintComponent(Graphics g) { + // Workaround: Add a new line at the end so that the caret is not in the visible area. + toolTipPane.setText(text + "\n"); + super.paintComponent(g); + } +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/tooltip/ToolTipAction.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/tooltip/ToolTipAction.java new file mode 100644 index 000000000000..a4bb61d15b93 --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/tooltip/ToolTipAction.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor.tooltip; + +import at.ssw.visualizer.texteditor.model.Scanner; +import at.ssw.visualizer.texteditor.model.Text; +import java.awt.event.ActionEvent; +import javax.swing.text.JTextComponent; +import org.netbeans.api.editor.EditorActionRegistration; +import org.netbeans.editor.BaseDocument; +import org.netbeans.editor.EditorUI; +import org.netbeans.editor.PopupManager; +import org.netbeans.editor.TokenID; +import org.netbeans.editor.Utilities; +import org.netbeans.editor.ext.ExtKit; +import org.netbeans.editor.ext.ToolTipSupport; +import org.netbeans.modules.editor.NbEditorKit; + +/** + * + * @author Christian Wimmer + * @author Alexander Reder + */ +public class ToolTipAction extends NbEditorKit.NbBuildToolTipAction { + public ToolTipAction() { + putValue(NAME, ExtKit.buildToolTipAction); + } + + @Override + public void actionPerformed(ActionEvent evt, JTextComponent target) { + if (!showTooltip(target)) { + super.actionPerformed(evt, target); + } + } + + private boolean showTooltip(JTextComponent target) { + BaseDocument document = Utilities.getDocument(target); + Text text = (Text) document.getProperty(Text.class); + if (text == null) { + return false; + } + + EditorUI ui = Utilities.getEditorUI(target); + ToolTipSupport tts = ui.getToolTipSupport(); + int offset = target.viewToModel(tts.getLastMouseEvent().getPoint()); + + String toolTipText = text.getRegionHover(offset); + + if (toolTipText == null) { + Scanner scanner = text.getScanner(); + scanner.setText(document); + scanner.findTokenBegin(offset); + TokenID token = scanner.nextToken(); + if (token.getNumericID() < 0) { + return false; + } + + toolTipText = text.getStringHover(scanner.getTokenString()); + if (toolTipText == null) { + return false; + } + } + + StyledToolTip tooltip = new StyledToolTip(toolTipText, target.getUI().getEditorKit(target)); + + tts.setToolTip(tooltip, PopupManager.ViewPortBounds, PopupManager.Largest, 0, 0); + return true; + } +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/view/AbstractTextViewTopComponent.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/view/AbstractTextViewTopComponent.java new file mode 100644 index 000000000000..db29a2d6146f --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/view/AbstractTextViewTopComponent.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor.view; + +import at.ssw.visualizer.core.selection.Selection; +import at.ssw.visualizer.core.selection.SelectionManager; +import at.ssw.visualizer.model.cfg.BasicBlock; +import at.ssw.visualizer.model.cfg.ControlFlowGraph; +import at.ssw.visualizer.texteditor.EditorKit; +import java.awt.BorderLayout; +import java.util.Arrays; +import javax.swing.BorderFactory; +import javax.swing.JEditorPane; +import javax.swing.JScrollPane; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.openide.windows.TopComponent; + +/** + * + * @author Alexander Reder + */ +public abstract class AbstractTextViewTopComponent extends TopComponent { + + protected ControlFlowGraph curCFG; + protected BasicBlock[] curBlocks; + + private JEditorPane editorPane; + + // EditorKit must be set in the imlementation class + public AbstractTextViewTopComponent(EditorKit kit) { + editorPane = new JEditorPane(); + editorPane.setEditorKit(kit); + editorPane.setEditable(false); + JScrollPane scrollPane = new JScrollPane(editorPane); + scrollPane.setViewportBorder(BorderFactory.createEmptyBorder()); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + setLayout(new BorderLayout()); + add(scrollPane); + } + + @Override + protected void componentShowing() { + super.componentShowing(); + SelectionManager.getDefault().addChangeListener(selectionChangeListener); + updateContent(); + } + + @Override + protected void componentHidden() { + super.componentHidden(); + SelectionManager.getDefault().removeChangeListener(selectionChangeListener); + curCFG = null; + curBlocks = null; + } + + + private ChangeListener selectionChangeListener = new ChangeListener() { + public void stateChanged(ChangeEvent event) { + updateContent(); + } + }; + + protected void updateContent() { + Selection selection = SelectionManager.getDefault().getCurSelection(); + ControlFlowGraph newCFG = selection.get(ControlFlowGraph.class); + BasicBlock[] newBlocks = selection.get(BasicBlock[].class); + + if (newCFG == null || newBlocks == null || newBlocks.length == 0) { + editorPane.setText("No block selected\n"); + } else if (curCFG != newCFG || !Arrays.equals(curBlocks, newBlocks)) { + editorPane.setText(getContent(newCFG, newBlocks)); + } + curCFG = newCFG; + curBlocks = newBlocks; + } + + protected abstract String getContent(ControlFlowGraph cfg, BasicBlock[] blocks); + +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/view/PaintBugWorkaroundBar.java b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/view/PaintBugWorkaroundBar.java new file mode 100644 index 000000000000..3b280085ceb8 --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/java/at/ssw/visualizer/texteditor/view/PaintBugWorkaroundBar.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.texteditor.view; + +import java.awt.Dimension; +import javax.swing.JComponent; +import javax.swing.text.JTextComponent; +import org.netbeans.api.editor.fold.FoldUtilities; +import org.netbeans.spi.editor.SideBarFactory; + +/** + * This is a workaround for NETBEANS-6232. NB platform wrongly computes sidebar + * (line numbers, code folding) after fold expansion - until the bug is fixed, this + * component will force the sidebar's height to the text view's height. + * It is safe to remove this class + layer.xml registration after NETBEANS-6232 is fixed and + * the fixed platform is consumed in C1V. + * @author sdedic + */ +public class PaintBugWorkaroundBar extends JComponent { + private final JTextComponent target; + + PaintBugWorkaroundBar(JTextComponent target) { + this.target = target; + } + + @Override + public Dimension getPreferredSize() { + Dimension d = new Dimension(); + d.height = target.getPreferredScrollableViewportSize().height; + return d; + } + + /** + * Factory for the sidebars. Use {@link FoldUtilities#getFoldingSidebarFactory()} to + * obtain an instance. + */ + public static class Factory implements SideBarFactory { + @Override + public JComponent createSideBar(JTextComponent target) { + return new PaintBugWorkaroundBar(target); + } + } +} diff --git a/visualizer/C1Visualizer/TextEditor/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/TextEditor/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..7a64f87674e1 --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer.texteditor +OpenIDE-Module-Layer: at/ssw/visualizer/texteditor/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/texteditor/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/TextEditor/src/main/resources/at/ssw/visualizer/texteditor/Bundle.properties b/visualizer/C1Visualizer/TextEditor/src/main/resources/at/ssw/visualizer/texteditor/Bundle.properties new file mode 100644 index 000000000000..d0b97b7ed427 --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/resources/at/ssw/visualizer/texteditor/Bundle.properties @@ -0,0 +1 @@ +OpenIDE-Module-Name=Text Editor diff --git a/visualizer/C1Visualizer/TextEditor/src/main/resources/at/ssw/visualizer/texteditor/layer.xml b/visualizer/C1Visualizer/TextEditor/src/main/resources/at/ssw/visualizer/texteditor/layer.xml new file mode 100644 index 000000000000..fa61fdc49e67 --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/resources/at/ssw/visualizer/texteditor/layer.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/TextEditor/src/main/resources/at/ssw/visualizer/texteditor/preferences.xml b/visualizer/C1Visualizer/TextEditor/src/main/resources/at/ssw/visualizer/texteditor/preferences.xml new file mode 100644 index 000000000000..c69c6f9d3186 --- /dev/null +++ b/visualizer/C1Visualizer/TextEditor/src/main/resources/at/ssw/visualizer/texteditor/preferences.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/visualizer/C1Visualizer/VisualizerUI/pom.xml b/visualizer/C1Visualizer/VisualizerUI/pom.xml new file mode 100644 index 000000000000..f414ec728a46 --- /dev/null +++ b/visualizer/C1Visualizer/VisualizerUI/pom.xml @@ -0,0 +1,73 @@ + + 4.0.0 + + C1Visualizer-parent + at.ssw.visualizer + 1.13-SNAPSHOT + + at.ssw.visualizer + VisualizerUI + 1.13-SNAPSHOT + nbm + VisualizerUI + + UTF-8 + + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-openide-util-lookup + ${netbeans.version} + + + org.netbeans.api + org-openide-windows + ${netbeans.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + + at.ssw.visualizer.core.selection + at.ssw.visualizer.core.focus + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/DefaultExample b/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/DefaultExample new file mode 100644 index 000000000000..47be9ba306c9 --- /dev/null +++ b/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/DefaultExample @@ -0,0 +1 @@ +Default Text diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/focus/Focus.java b/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/focus/Focus.java new file mode 100644 index 000000000000..59b342b42b50 --- /dev/null +++ b/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/focus/Focus.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package at.ssw.visualizer.core.focus; + +import at.ssw.visualizer.core.selection.SelectionProvider; +import org.openide.windows.TopComponent; + +public class Focus { + public static interface SelectionClosure { + public boolean matches(TopComponent tc); + } + + public static boolean findEditor(Class c, Object data) { + for (TopComponent tc : TopComponent.getRegistry().getOpened()) { + if (tc.getClass() == c && ((SelectionProvider)tc).getSelection().get(data.getClass()) == data) { +// WindowManager wm = WindowManager.getDefault(); +// Mode mode = wm.findMode(tc); +// if(mode != null && mode != wm.getCurrentMaximizedMode()) { +// wm.switchMaximizedMode(null); +// } + tc.requestActive(); + return true; + } + } + return false; + } +} diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/selection/Selection.java b/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/selection/Selection.java new file mode 100644 index 000000000000..7f3008978e0a --- /dev/null +++ b/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/selection/Selection.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.core.selection; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.swing.Timer; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +/** + * + * @author Christian Wimmer + */ +public class Selection { + private Map elements; + private List listeners; + private Timer eventTimer; + + private ActionListener eventTimerListener = new ActionListener() { + public void actionPerformed(ActionEvent event) { + doFireChangeEvent(); + } + }; + + public Selection() { + elements = new HashMap(); + listeners = new ArrayList(); + eventTimer = new Timer(100, eventTimerListener); + eventTimer.setRepeats(false); + } + + private void doPut(Class clazz, Object element) { + elements.put(clazz, element); + for (Class i : clazz.getInterfaces()) { + doPut(i, element); + } + } + + public void put(Object element) { + doPut(element.getClass(), element); + fireChangeEvent(); + SelectionManager.getDefault().fireChangeEvent(); + } + + @SuppressWarnings(value = "unchecked") + public T get(Class clazz) { + return (T) elements.get(clazz); + } + + + protected void doFireChangeEvent() { + ChangeEvent event = new ChangeEvent(this); + for (ChangeListener listener : listeners.toArray(new ChangeListener[listeners.size()])) { + listener.stateChanged(event); + } + } + + protected void fireChangeEvent() { + eventTimer.restart(); + } + + public void addChangeListener(ChangeListener listener) { + listeners.add(listener); + } + + public void removeChangeListener(ChangeListener listener) { + listeners.remove(listener); + } +} diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/selection/SelectionManager.java b/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/selection/SelectionManager.java new file mode 100644 index 000000000000..94dbe2594f83 --- /dev/null +++ b/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/selection/SelectionManager.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.core.selection; + +import javax.swing.event.ChangeListener; + +/** + * + * @author Christian Wimmer + */ +public class SelectionManager { + private static final SelectionManager SINGLETON = new SelectionManager(); + + public static SelectionManager getDefault() { + return SINGLETON; + } + + + /** Default selection returned when no TopComponent is active. + * It is also used to maintain listeners added to the selection manager. */ + private final Selection emptySelection; + private Selection curSelection; + + private SelectionManager() { + emptySelection = new Selection(); + curSelection = emptySelection; + } + + public Selection getCurSelection() { + return curSelection; + } + + public void setSelection(Selection sel) { + if (curSelection != sel) { + curSelection = sel; + fireChangeEvent(); + } + } + + public void removeSelection(Selection sel) { + if (curSelection == sel) { + curSelection = emptySelection; + fireChangeEvent(); + } + } + + protected void fireChangeEvent() { + emptySelection.fireChangeEvent(); + } + + public void addChangeListener(ChangeListener listener) { + emptySelection.addChangeListener(listener); + } + + public void removeChangeListener(ChangeListener listener) { + emptySelection.removeChangeListener(listener); + } +} diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/selection/SelectionProvider.java b/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/selection/SelectionProvider.java new file mode 100644 index 000000000000..8829b664fc0c --- /dev/null +++ b/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/core/selection/SelectionProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.core.selection; + +/** + * + * @author Christian Wimmer + */ +public interface SelectionProvider { + public Selection getSelection(); +} diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/nbm/manifest.mf b/visualizer/C1Visualizer/VisualizerUI/src/main/nbm/manifest.mf new file mode 100644 index 000000000000..b83b2bb50196 --- /dev/null +++ b/visualizer/C1Visualizer/VisualizerUI/src/main/nbm/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: at.ssw.visualizer +OpenIDE-Module-Layer: at/ssw/visualizer/layer.xml +OpenIDE-Module-Localizing-Bundle: at/ssw/visualizer/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/Bundle.properties b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/Bundle.properties new file mode 100644 index 000000000000..5c3e441e9896 --- /dev/null +++ b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/Bundle.properties @@ -0,0 +1,23 @@ +OpenIDE-Module-Name=Visualizer UI + +# Coloring profile name +Editors/FontsColors/NetBeans=Default + +# Syntax Colorings +default=Default + +# Highlighting +selection=Selected Text +guarded=Guarded Block +block-search=Search Block +status-bar=Status Bar +status-bar-bold=Bold Status Bar +inc-search=Incremental Search +line-number=Line Number +code-folding-bar=Code Folding Bar +code-folding=Code Folding +highlight-search=Highlight Search +highlight-caret-row=Highlight Caret Row +caret-color-insert-mode=Caret Color +text-limit-line-color=Text Limit Line +at-ssw-visualizer-highlight=Reference Highlights diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/NetBeans-editor.xml b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/NetBeans-editor.xml new file mode 100644 index 000000000000..5086088e79ea --- /dev/null +++ b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/NetBeans-editor.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/NetBeans-fontsColors.xml b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/NetBeans-fontsColors.xml new file mode 100644 index 000000000000..7597a125b1ee --- /dev/null +++ b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/NetBeans-fontsColors.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/bottomLeftWsmode.xml b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/bottomLeftWsmode.xml new file mode 100644 index 000000000000..aa9272b22dbb --- /dev/null +++ b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/bottomLeftWsmode.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/bottomRightWsmode.xml b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/bottomRightWsmode.xml new file mode 100644 index 000000000000..b11a01168fb6 --- /dev/null +++ b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/bottomRightWsmode.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/layer.xml b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/layer.xml new file mode 100644 index 000000000000..1be5a1c51cc0 --- /dev/null +++ b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/layer.xml @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/leftWsmode.xml b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/leftWsmode.xml new file mode 100644 index 000000000000..5927985a41b9 --- /dev/null +++ b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/leftWsmode.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/visualizer/C1Visualizer/application/pom.xml b/visualizer/C1Visualizer/application/pom.xml new file mode 100644 index 000000000000..5da0a2f73c72 --- /dev/null +++ b/visualizer/C1Visualizer/application/pom.xml @@ -0,0 +1,1202 @@ + + + + + 4.0.0 + + at.ssw.visualizer + C1Visualizer-parent + 1.13-SNAPSHOT + + C1Visualizer-app + nbm-application + C1Visualizer-app + + UTF-8 + ${project.build.directory}/${brandingToken} + + + + org.netbeans.cluster + ide + ${netbeans.version} + pom + + + org.netbeans.api + org-netbeans-libs-tomlj + + + org.netbeans.modules + org-netbeans-modules-project-dependency + + + org.netbeans.modules + org-netbeans-modules-languages-toml + + + org.netbeans.modules + org-netbeans-modules-languages-go + + + org.netbeans.modules + org-netbeans-modules-languages-hcl + + + org.netbeans.external + com-jcraft-jsch + + + org.netbeans.external + com-jcraft-jzlib + + + org.netbeans.external + org-apache-commons-httpclient + + + org.netbeans.external + org-apache-ws-commons-util + + + org.netbeans.api + org-apache-xml-resolver + + + org.netbeans.external + org-apache-xmlrpc + + + org.netbeans.external + org-eclipse-core-net + + + org.netbeans.external + org-eclipse-core-runtime-compatibility-auth + + + org.netbeans.external + org-eclipse-equinox-preferences + + + org.netbeans.external + org-eclipse-equinox-registry + + + org.netbeans.external + org-eclipse-equinox-security + + + org.netbeans.external + org-eclipse-jgit + + + org.netbeans.external + org-eclipse-mylyn-bugzilla-core + + + org.netbeans.external + org-eclipse-mylyn-commons-core + + + org.netbeans.external + org-eclipse-mylyn-commons-net + + + org.netbeans.external + org-eclipse-mylyn-commons-xmlrpc + + + org.netbeans.external + org-eclipse-mylyn-tasks-core + + + org.netbeans.api + org-netbeans-api-debugger + + + org.netbeans.api + org-netbeans-api-java-classpath + + + org.netbeans.api + org-netbeans-api-xml-ui + + + org.netbeans.api + org-netbeans-api-xml + + + org.netbeans.modules + org-netbeans-core-browser-webview + + + org.netbeans.modules + org-netbeans-core-browser + + + org.netbeans.api + org-netbeans-core-ide + + + org.netbeans.modules + org-netbeans-core-multitabs-project + + + org.netbeans.modules + org-netbeans-lib-terminalemulator + + + org.netbeans.api + org-netbeans-libs-antlr3-runtime + + + org.netbeans.api + org-netbeans-libs-commons_net + + + org.netbeans.modules + org-netbeans-libs-commons_compress + + + org.netbeans.modules + org-netbeans-libs-freemarker + + + org.netbeans.api + org-netbeans-libs-git + + + org.netbeans.modules + org-netbeans-libs-ini4j + + + org.netbeans.modules + org-netbeans-libs-jaxb + + + org.netbeans.modules + org-netbeans-libs-jsch-agentproxy + + + org.netbeans.api + org-netbeans-libs-lucene + + + org.netbeans.api + org-netbeans-libs-smack + + + org.netbeans.modules + org-netbeans-libs-svnClientAdapter-javahl + + + org.netbeans.api + org-netbeans-libs-svnClientAdapter + + + org.netbeans.api + org-netbeans-libs-xerces + + + org.netbeans.modules + org-netbeans-modules-bugtracking-bridge + + + org.netbeans.modules + org-netbeans-modules-bugtracking-commons + + + org.netbeans.api + org-netbeans-modules-bugtracking + + + org.netbeans.modules + org-netbeans-modules-bugzilla + + + org.netbeans.modules + org-netbeans-modules-code-analysis + + + org.netbeans.api + org-netbeans-modules-csl-api + + + org.netbeans.api + org-netbeans-modules-csl-types + + + org.netbeans.modules + org-netbeans-modules-css-editor + + + org.netbeans.modules + org-netbeans-modules-css-lib + + + org.netbeans.modules + org-netbeans-modules-css-model + + + org.netbeans.modules + org-netbeans-modules-css-prep + + + org.netbeans.modules + org-netbeans-modules-css-visual + + + org.netbeans.api + org-netbeans-modules-db-core + + + org.netbeans.modules + org-netbeans-modules-db-dataview + + + org.netbeans.modules + org-netbeans-modules-db-drivers + + + org.netbeans.modules + org-netbeans-modules-db-kit + + + org.netbeans.api + org-netbeans-modules-db-metadata-model + + + org.netbeans.modules + org-netbeans-modules-db-mysql + + + org.netbeans.modules + org-netbeans-modules-db-sql-editor + + + org.netbeans.modules + org-netbeans-modules-db-sql-visualeditor + + + org.netbeans.api + org-netbeans-modules-db + + + org.netbeans.modules + org-netbeans-modules-dbapi + + + org.netbeans.modules + org-netbeans-modules-defaults + + + org.netbeans.modules + org-netbeans-modules-derby + + + org.netbeans.api + org-netbeans-modules-diff + + + org.netbeans.modules + org-netbeans-modules-dlight-nativeexecution-nb + + + org.netbeans.modules + org-netbeans-modules-dlight-nativeexecution + + + org.netbeans.modules + org-netbeans-modules-dlight-terminal + + + org.netbeans.modules + org-netbeans-modules-docker-api + + + org.netbeans.modules + org-netbeans-modules-docker-editor + + + org.netbeans.modules + org-netbeans-modules-docker-ui + + + org.netbeans.modules + org-netbeans-modules-editor-bookmarks + + + org.netbeans.api + org-netbeans-modules-editor-bracesmatching + + + org.netbeans.modules + org-netbeans-modules-editor-breadcrumbs + + + org.netbeans.api + org-netbeans-modules-editor-codetemplates + + + org.netbeans.api + org-netbeans-modules-editor-completion + + + org.netbeans.api + org-netbeans-modules-editor-errorstripe-api + + + org.netbeans.modules + org-netbeans-modules-editor-errorstripe + + + org.netbeans.modules + org-netbeans-modules-editor-global-format + + + org.netbeans.api + org-netbeans-modules-editor-guards + + + org.netbeans.api + org-netbeans-modules-editor-indent-project + + + org.netbeans.api + org-netbeans-modules-editor-indent + + + org.netbeans.modules + org-netbeans-modules-editor-kit + + + org.netbeans.modules + org-netbeans-modules-editor-macros + + + org.netbeans.api + org-netbeans-modules-editor-plain-lib + + + org.netbeans.modules + org-netbeans-modules-editor-plain + + + org.netbeans.modules + org-netbeans-modules-editor-search + + + org.netbeans.modules + org-netbeans-modules-editor-structure + + + org.netbeans.api + org-netbeans-modules-editor-tools-storage + + + org.netbeans.modules + org-netbeans-modules-extbrowser + + + org.netbeans.modules + org-netbeans-modules-extexecution-impl + + + org.netbeans.api + org-netbeans-modules-extexecution + + + org.netbeans.modules + org-netbeans-modules-git + + + org.netbeans.modules + org-netbeans-modules-gototest + + + org.netbeans.modules + org-netbeans-modules-gsf-codecoverage + + + org.netbeans.modules + org-netbeans-modules-gsf-testrunner-ui + + + org.netbeans.api + org-netbeans-modules-gsf-testrunner + + + org.netbeans.modules + org-netbeans-modules-html-custom + + + org.netbeans.modules + org-netbeans-modules-html-editor-lib + + + org.netbeans.modules + org-netbeans-modules-html-editor + + + org.netbeans.api + org-netbeans-modules-html-indexing + + + org.netbeans.api + org-netbeans-modules-html-lexer + + + org.netbeans.modules + org-netbeans-modules-html-parser + + + org.netbeans.modules + org-netbeans-modules-html-validation + + + org.netbeans.modules + org-netbeans-modules-html + + + org.netbeans.modules + org-netbeans-modules-httpserver + + + org.netbeans.modules + org-netbeans-modules-hudson-git + + + org.netbeans.modules + org-netbeans-modules-hudson-mercurial + + + org.netbeans.modules + org-netbeans-modules-hudson-subversion + + + org.netbeans.modules + org-netbeans-modules-hudson-tasklist + + + org.netbeans.modules + org-netbeans-modules-hudson-ui + + + org.netbeans.modules + org-netbeans-modules-hudson + + + org.netbeans.modules + org-netbeans-modules-ide-kit + + + org.netbeans.modules + org-netbeans-modules-image + + + org.netbeans.modules + org-netbeans-modules-javascript2-debug-ui + + + org.netbeans.modules + org-netbeans-modules-javascript2-debug + + + org.netbeans.api + org-netbeans-modules-jellytools-ide + + + org.netbeans.api + org-netbeans-modules-jumpto + + + org.netbeans.modules + org-netbeans-modules-languages-diff + + + org.netbeans.modules + org-netbeans-modules-languages-manifest + + + org.netbeans.modules + org-netbeans-modules-languages-yaml + + + org.netbeans.api + org-netbeans-modules-languages + + + org.netbeans.modules + org-netbeans-modules-localhistory + + + org.netbeans.modules + org-netbeans-modules-localtasks + + + org.netbeans.api + org-netbeans-modules-lsp-client + + + org.netbeans.modules + org-netbeans-modules-markdown + + + org.netbeans.modules + org-netbeans-modules-mercurial + + + org.netbeans.modules + org-netbeans-modules-mylyn-util + + + org.netbeans.modules + org-netbeans-modules-nativeimage-api + + + org.netbeans.modules + org-netbeans-modules-notifications + + + org.netbeans.api + org-netbeans-modules-parsing-api + + + org.netbeans.api + org-netbeans-modules-parsing-indexing + + + org.netbeans.modules + org-netbeans-modules-parsing-lucene + + + org.netbeans.modules + org-netbeans-modules-parsing-nb + + + org.netbeans.modules + org-netbeans-modules-parsing-ui + + + org.netbeans.modules + org-netbeans-modules-print-editor + + + org.netbeans.api + org-netbeans-modules-project-ant-compat8 + + + org.netbeans.api + org-netbeans-modules-project-ant-ui + + + org.netbeans.api + org-netbeans-modules-project-ant + + + org.netbeans.modules + org-netbeans-modules-project-indexingbridge + + + org.netbeans.api + org-netbeans-modules-project-libraries-ui + + + org.netbeans.api + org-netbeans-modules-project-libraries + + + org.netbeans.modules + org-netbeans-modules-project-spi-intern-impl + + + org.netbeans.modules + org-netbeans-modules-project-spi-intern + + + org.netbeans.modules + org-netbeans-modules-projectapi-nb + + + org.netbeans.api + org-netbeans-modules-projectapi + + + org.netbeans.modules + org-netbeans-modules-projectui-buildmenu + + + org.netbeans.modules + org-netbeans-modules-projectui + + + org.netbeans.api + org-netbeans-modules-projectuiapi-base + + + org.netbeans.api + org-netbeans-modules-projectuiapi + + + org.netbeans.api + org-netbeans-modules-properties-syntax + + + org.netbeans.modules + org-netbeans-modules-properties + + + org.netbeans.api + org-netbeans-modules-refactoring-api + + + org.netbeans.api + org-netbeans-modules-schema2beans + + + org.netbeans.modules + org-netbeans-modules-selenium2-server + + + org.netbeans.modules + org-netbeans-modules-selenium2 + + + org.netbeans.api + org-netbeans-modules-server + + + org.netbeans.api + org-netbeans-modules-servletapi + + + org.netbeans.api + org-netbeans-modules-spellchecker-apimodule + + + org.netbeans.modules + org-netbeans-modules-spellchecker-bindings-htmlxml + + + org.netbeans.modules + org-netbeans-modules-spellchecker-bindings-properties + + + org.netbeans.modules + org-netbeans-modules-spellchecker-dictionary_en + + + org.netbeans.modules + org-netbeans-modules-spellchecker-kit + + + org.netbeans.modules + org-netbeans-modules-spellchecker + + + org.netbeans.modules + org-netbeans-modules-subversion + + + org.netbeans.modules + org-netbeans-modules-swing-validation + + + org.netbeans.modules + org-netbeans-modules-target-iterator + + + org.netbeans.modules + org-netbeans-modules-tasklist-kit + + + org.netbeans.modules + org-netbeans-modules-tasklist-projectint + + + org.netbeans.modules + org-netbeans-modules-tasklist-todo + + + org.netbeans.modules + org-netbeans-modules-tasklist-ui + + + org.netbeans.modules + org-netbeans-modules-team-commons + + + org.netbeans.modules + org-netbeans-modules-team-ide + + + org.netbeans.modules + org-netbeans-modules-terminal-nb + + + org.netbeans.modules + org-netbeans-modules-terminal + + + org.netbeans.modules + org-netbeans-modules-usersguide + + + org.netbeans.modules + org-netbeans-modules-utilities-project + + + org.netbeans.modules + org-netbeans-modules-utilities + + + org.netbeans.modules + org-netbeans-modules-versioning-core + + + org.netbeans.modules + org-netbeans-modules-versioning-indexingbridge + + + org.netbeans.modules + org-netbeans-modules-versioning-masterfs + + + org.netbeans.modules + org-netbeans-modules-versioning-system-cvss-installer + + + org.netbeans.modules + org-netbeans-modules-versioning-ui + + + org.netbeans.modules + org-netbeans-modules-versioning-util + + + org.netbeans.api + org-netbeans-modules-versioning + + + org.netbeans.modules + org-netbeans-modules-web-browser-api + + + org.netbeans.modules + org-netbeans-modules-web-common-ui + + + org.netbeans.modules + org-netbeans-modules-web-common + + + org.netbeans.modules + org-netbeans-modules-web-indent + + + org.netbeans.modules + org-netbeans-modules-web-webkit-debugging + + + org.netbeans.modules + org-netbeans-modules-xml-axi + + + org.netbeans.api + org-netbeans-modules-xml-catalog-ui + + + org.netbeans.api + org-netbeans-modules-xml-catalog + + + org.netbeans.api + org-netbeans-modules-xml-core + + + org.netbeans.api + org-netbeans-modules-xml-jaxb-api + + + org.netbeans.api + org-netbeans-modules-xml-lexer + + + org.netbeans.modules + org-netbeans-modules-xml-multiview + + + org.netbeans.api + org-netbeans-modules-xml-retriever + + + org.netbeans.api + org-netbeans-modules-xml-schema-completion + + + org.netbeans.api + org-netbeans-modules-xml-schema-model + + + org.netbeans.modules + org-netbeans-modules-xml-tax + + + org.netbeans.api + org-netbeans-modules-xml-text-obsolete90 + + + org.netbeans.modules + org-netbeans-modules-xml-text + + + org.netbeans.modules + org-netbeans-modules-xml-tools + + + org.netbeans.api + org-netbeans-modules-xml-wsdl-model + + + org.netbeans.api + org-netbeans-modules-xml-xam + + + org.netbeans.api + org-netbeans-modules-xml-xdm + + + org.netbeans.modules + org-netbeans-modules-xml + + + org.netbeans.modules + org-netbeans-modules-xsl + + + org.netbeans.api + org-netbeans-spi-debugger-ui + + + org.netbeans.api + org-netbeans-spi-editor-hints-projects + + + org.netbeans.api + org-netbeans-spi-editor-hints + + + org.netbeans.api + org-netbeans-spi-navigator + + + org.netbeans.api + org-netbeans-spi-palette + + + org.netbeans.api + org-netbeans-spi-tasklist + + + org.netbeans.api + org-netbeans-spi-viewmodel + + + org.netbeans.modules + org-netbeans-swing-dirchooser + + + org.netbeans.api + org-openidex-util + + + + + org.netbeans.cluster + platform + ${netbeans.version} + pom + + + org.netbeans.external + org-apache-commons-codec + + + org.netbeans.external + org-apache-commons-logging + + + org.netbeans.api + org-netbeans-core-multiview + + + org.netbeans.modules + org-netbeans-core-nativeaccess + + + org.netbeans.api + org-netbeans-core-netigso + + + org.netbeans.modules + org-netbeans-core-osgi + + + org.netbeans.modules + org-netbeans-core-output2 + + + org.netbeans.modules + org-netbeans-libs-felix + + + org.netbeans.api + org-netbeans-libs-jsr223 + + + org.netbeans.api + org-netbeans-libs-junit4 + + + org.netbeans.api + org-netbeans-libs-osgi + + + org.netbeans.api + org-netbeans-libs-testng + + + org.netbeans.modules + org-netbeans-modules-autoupdate-cli + + + org.netbeans.api + org-netbeans-modules-autoupdate-services + + + org.netbeans.api + org-netbeans-modules-autoupdate-ui + + + org.netbeans.modules + org-netbeans-modules-core-kit + + + org.netbeans.modules + org-netbeans-modules-favorites + + + org.netbeans.api + org-netbeans-modules-javahelp + + + org.netbeans.modules + org-netbeans-modules-junitlib + + + org.netbeans.modules + org-netbeans-modules-keyring-impl + + + org.netbeans.api + org-netbeans-modules-netbinox + + + org.netbeans.api + org-netbeans-modules-print + + + org.netbeans.api + org-netbeans-modules-sendopts + + + org.netbeans.modules + org-netbeans-modules-templates + + + org.netbeans.api + org-openide-compat + + + org.netbeans.api + org-apache-commons-commons_io + + + + + ${project.groupId} + branding + ${project.version} + + + ${project.groupId} + BlockView + ${project.version} + + + ${project.groupId} + BytecodeEditor + ${project.version} + + + ${project.groupId} + BytecodeModel + ${project.version} + + + ${project.groupId} + BytecodeView + ${project.version} + + + ${project.groupId} + CompilationModel + ${project.version} + + + ${project.groupId} + CompilationView + ${project.version} + + + ${project.groupId} + ControlFlowEditor + ${project.version} + + + ${project.groupId} + DataFlowEditor + ${project.version} + + + ${project.groupId} + DataFlowGraph + ${project.version} + + + ${project.groupId} + DataFlowView + ${project.version} + + + ${project.groupId} + GraphHelper + ${project.version} + + + ${project.groupId} + GraphLayoutAPI + ${project.version} + + + ${project.groupId} + GraphLayoutImpl + ${project.version} + + + ${project.groupId} + IntermediateCodeEditor + ${project.version} + + + ${project.groupId} + IntermediateCodeViews + ${project.version} + + + ${project.groupId} + IntervalEditor + ${project.version} + + + ${project.groupId} + IntervalView + ${project.version} + + + ${project.groupId} + NativeCodeEditor + ${project.version} + + + ${project.groupId} + NativeCodeView + ${project.version} + + + ${project.groupId} + TextEditor + ${project.version} + + + ${project.groupId} + VisualizerUI + ${project.version} + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + + src/main/resources/${brandingToken}.conf + src/main/resources/${brandingToken}.clusters + + + + + + + deployment + + + + org.apache.netbeans.utilities + nbm-maven-plugin + + + extra + + autoupdate + webstart-app + build-installers + + + + + + + + + diff --git a/visualizer/C1Visualizer/application/src/main/resources/c1visualizer.clusters b/visualizer/C1Visualizer/application/src/main/resources/c1visualizer.clusters new file mode 100644 index 000000000000..f1bb4b1abf03 --- /dev/null +++ b/visualizer/C1Visualizer/application/src/main/resources/c1visualizer.clusters @@ -0,0 +1,2 @@ +c1visualizer +ide diff --git a/visualizer/C1Visualizer/application/src/main/resources/c1visualizer.conf b/visualizer/C1Visualizer/application/src/main/resources/c1visualizer.conf new file mode 100644 index 000000000000..9acc1953512c --- /dev/null +++ b/visualizer/C1Visualizer/application/src/main/resources/c1visualizer.conf @@ -0,0 +1,37 @@ +# Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. + +# ${HOME} will be replaced by user home directory according to platform + +default_userdir="${DEFAULT_USERDIR_ROOT}/0.31" +default_cachedir="${DEFAULT_CACHEDIR_ROOT}/0.31" + +# options used by the launcher by default, can be overridden by explicit +# command line switches +default_options="--branding c1visualizer -J-XX:+UseStringDeduplication -J-DRepositoryUpdate.increasedLogLevel=800 -J-client -J-Xss2m -J-Xms32m -J-Dnetbeans.logger.console=true -J-Djdk.gtk.version=2.2 -J-Dapple.laf.useScreenMenuBar=true -J-Dapple.awt.graphics.UseQuartz=true -J-Dsun.java2d.noddraw=true -J-Dsun.java2d.dpiaware=true -J-Dsun.zip.disableMemoryMapping=true -J-Dplugin.manager.check.updates=false -J--add-opens=java.base/java.net=ALL-UNNAMED -J--add-opens=java.base/java.lang.ref=ALL-UNNAMED -J--add-opens=java.base/java.lang=ALL-UNNAMED -J--add-opens=java.base/java.security=ALL-UNNAMED -J--add-opens=java.base/java.util=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing.text=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing=ALL-UNNAMED -J--add-opens=java.desktop/java.awt=ALL-UNNAMED -J--add-opens=java.desktop/java.awt.event=ALL-UNNAMED -J--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED -J--add-opens=jdk.jshell/jdk.jshell=ALL-UNNAMED -J--add-modules=jdk.jshell -J--add-exports=java.desktop/sun.awt=ALL-UNNAMED -J--add-exports=java.desktop/java.awt.peer=ALL-UNNAMED -J--add-exports=java.desktop/com.sun.beans.editors=ALL-UNNAMED -J--add-exports=java.desktop/sun.swing=ALL-UNNAMED -J--add-exports=java.desktop/sun.awt.im=ALL-UNNAMED -J--add-exports=jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED -J--add-exports=java.management/sun.management=ALL-UNNAMED -J--add-exports=java.base/sun.reflect.annotation=ALL-UNNAMED -J--add-exports=jdk.javadoc/com.sun.tools.javadoc.main=ALL-UNNAMED -J--enable-native-access=ALL-UNNAMED -J-XX:+IgnoreUnrecognizedVMOptions" + +# for development purposes you may wish to append: -J-Dnetbeans.logger.console=true -J-ea + +# default location of JDK/JRE, can be overridden by using --jdkhome

switch +#jdkhome="/path/to/jdk" + +# clusters' paths separated by path.separator (semicolon on Windows, colon on Unices) +#extra_clusters= diff --git a/visualizer/C1Visualizer/branding.jnlp b/visualizer/C1Visualizer/branding.jnlp new file mode 100644 index 000000000000..ee4c91c4888e --- /dev/null +++ b/visualizer/C1Visualizer/branding.jnlp @@ -0,0 +1,15 @@ + + + + + ${app.title} + ${app.title} vendor + ${app.name} application + + + ${jnlp.permissions} + + ${jnlp.branding.jars} + + + diff --git a/visualizer/C1Visualizer/branding/pom.xml b/visualizer/C1Visualizer/branding/pom.xml new file mode 100644 index 000000000000..b7065d2bef57 --- /dev/null +++ b/visualizer/C1Visualizer/branding/pom.xml @@ -0,0 +1,97 @@ + + + + + 4.0.0 + + at.ssw.visualizer + C1Visualizer-parent + 1.13-SNAPSHOT + + at.ssw.visualizer + branding + 1.13-SNAPSHOT + nbm + branding + + UTF-8 + + + + + ${basedir}/src/main/nbm-branding + + + **/* + + true + ${basedir}/target/filtered-nbm-branding + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + ${project.build.sourceEncoding} + + gif + png + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + + + ${basedir}/target/filtered-nbm-branding + + + + org.apache.maven.plugins + maven-jar-plugin + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + diff --git a/visualizer/C1Visualizer/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/visualizer/C1Visualizer/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/Bundle.properties new file mode 100644 index 000000000000..e6d83cc4b8cf --- /dev/null +++ b/visualizer/C1Visualizer/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -0,0 +1,8 @@ +#Updated by 'ant build-brand' +#Mon, 01 Nov 2021 20:27:18 +0100 +currentVersion=Java HotSpot Client Compiler Visualizer ${project.version} +LBL_splash_window_title=Starting Java HotSpot Client Compiler Visualizer +SplashProgressBarBounds=0,258,472,3 +SplashProgressBarColor=0x80 +SplashRunningTextBounds=15,245,452,12 +SplashRunningTextColor=0x80 diff --git a/visualizer/C1Visualizer/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/frame.gif b/visualizer/C1Visualizer/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/frame.gif new file mode 100644 index 0000000000000000000000000000000000000000..3882bd93ba1b8bdb57d7cde50ad6d556be904a6b GIT binary patch literal 667 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 literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/splash.gif b/visualizer/C1Visualizer/branding/src/main/nbm-branding/core/core.jar/org/netbeans/core/startup/splash.gif new file mode 100644 index 0000000000000000000000000000000000000000..2d219c518763bfc84e6d7066d09fc8a696f78947 GIT binary patch literal 127636 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 z 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 + + + + ${app.title} + ${app.title} vendor + ${app.name} application + + + + + + + + + + + + + + + --branding + ${branding.token} + + diff --git a/visualizer/C1Visualizer/pom.xml b/visualizer/C1Visualizer/pom.xml new file mode 100644 index 000000000000..32c0ca5e1900 --- /dev/null +++ b/visualizer/C1Visualizer/pom.xml @@ -0,0 +1,150 @@ + + + + + 4.0.0 + at.ssw.visualizer + C1Visualizer-parent + 1.13-SNAPSHOT + pom + C1Visualizer-parent + + + scm:git:git@github.com:oracle/graal.git + + HEAD + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + org.apache.netbeans.utilities + nbm-maven-plugin + ${nbmmvnplugin.version} + true + + ${brandingToken} + ${brandingToken} + + + + org.apache.maven.plugins + maven-compiler-plugin + ${mvncompilerplugin.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${mvnjarplugin.version} + + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + true + false + c1visualizer-@{project.version} + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${mvnenforcerplugin.version} + + + enforce-java + + enforce + + + + + [17,) + IdealGraphVisualizer requires a JDK version greater than or equal to 17 + + + + + + + + + + + BlockView + BytecodeEditor + BytecodeModel + BytecodeView + CompilationModel + CompilationView + ControlFlowEditor + DataFlowEditor + DataFlowGraph + DataFlowView + GraphHelper + GraphLayoutAPI + GraphLayoutImpl + IntermediateCodeEditor + IntermediateCodeViews + IntervalEditor + IntervalView + NativeCodeEditor + NativeCodeView + TextEditor + VisualizerUI + branding + application + + + RELEASE260 + 1.0.2 + 4.8 + 3.10.1 + 3.2.2 + 3.3.0 + 4.13.2 + 1.14 + 1.3.29 + 1.5.8 + c1visualizer + + diff --git a/visualizer/IdealGraphVisualizer/pom.xml b/visualizer/IdealGraphVisualizer/pom.xml index f3f03458dda2..9e729f303cff 100644 --- a/visualizer/IdealGraphVisualizer/pom.xml +++ b/visualizer/IdealGraphVisualizer/pom.xml @@ -80,7 +80,7 @@ - [17,24) + [17,24] The IGV Netbeans platform requires a JDK version between 17 and 24 From 18413b4da530e66b9870831202e884e06264831d Mon Sep 17 00:00:00 2001 From: Tom Rodriguez Date: Thu, 23 Oct 2025 10:10:12 -0700 Subject: [PATCH 03/11] Support long file offsets in parser --- .../java/at/ssw/visualizer/parser/Parser.java | 6 +- .../at/ssw/visualizer/parser/Scanner.java | 135 +++++++++--------- 2 files changed, 72 insertions(+), 69 deletions(-) diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Parser.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Parser.java index fb141032b52f..38b1ba9de1ae 100644 --- a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Parser.java +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Parser.java @@ -273,7 +273,7 @@ void Bytecodes(ControlFlowGraphImpl cfg) { String StringValue() { String res; Expect(53); - int beg = la.pos; + long beg = la.pos; while (StartOf(2)) { Get(); } @@ -500,7 +500,7 @@ IRInstructionImpl HIRInstruction() { String FreeValue() { String res; - int beg = la.pos; + long beg = la.pos; while (StartOf(3)) { Get(); } @@ -582,7 +582,7 @@ UsePositionImpl UsePosition() { String NoTrimFreeValue() { String res; - int beg = la.pos; + long beg = la.pos; while (StartOf(3)) { Get(); } diff --git a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Scanner.java b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Scanner.java index cb979bb9bb5d..3d312d3b811c 100644 --- a/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Scanner.java +++ b/visualizer/C1Visualizer/CompilationModel/src/main/java/at/ssw/visualizer/parser/Scanner.java @@ -32,8 +32,8 @@ class Token { public int kind; // token kind - public int pos; // token position in bytes in the source text (starting at 0) - public int charPos; // token position in characters in the source text (starting at 0) + public long pos; // token position in bytes in the source text (starting at 0) + public long charPos; // token position in characters in the source text (starting at 0) public int col; // token column (starting at 1) public int line; // token line (starting at 1) public String val; // token value @@ -54,9 +54,9 @@ class Buffer { private static final int MIN_BUFFER_LENGTH = 1024; // 1KB private static final int MAX_BUFFER_LENGTH = MIN_BUFFER_LENGTH * 64; // 64KB private byte[] buf; // input buffer - private int bufStart; // position of first byte in buffer relative to input stream + private long bufStart; // position of first byte in buffer relative to input stream private int bufLen; // length of buffer - private int fileLen; // length of input stream (may change if stream is no file) + private long fileLen; // length of input stream (may change if stream is no file) private int bufPos; // current position in buffer private RandomAccessFile file; // input stream (seekable) private InputStream stream; // growing input stream (e.g.: console, network) @@ -65,7 +65,10 @@ class Buffer { public Buffer(InputStream s) { stream = s; - fileLen = bufLen = bufStart = bufPos = 0; + fileLen = 0; + bufLen = 0; + bufStart = 0; + bufPos = 0; buf = new byte[MIN_BUFFER_LENGTH]; } @@ -73,13 +76,13 @@ public Buffer(String fileName, ProgressHandle progressHandle) { this.progressHandle = progressHandle; try { file = new RandomAccessFile(fileName, "r"); - fileLen = (int) file.length(); + fileLen = file.length(); if (progressHandle != null) { progressHandle.start((int)(fileLen / MAX_BUFFER_LENGTH)); } - bufLen = Math.min(fileLen, MAX_BUFFER_LENGTH); + bufLen = (int) Math.min(fileLen, MAX_BUFFER_LENGTH); buf = new byte[bufLen]; - bufStart = Integer.MAX_VALUE; // nothing in buffer so far + bufStart = Long.MAX_VALUE; // nothing in buffer so far if (fileLen > 0) setPos(0); // setup buffer to position 0 (start) else bufPos = 0; // index 0 is already after the file, thus setPos(0) is invalid if (bufLen == fileLen) Close(); @@ -132,7 +135,7 @@ public int Read() { } public int Peek() { - int curPos = getPos(); + long curPos = getPos(); int ch = Read(); setPos(curPos); return ch; @@ -140,21 +143,21 @@ public int Peek() { // beg .. begin, zero-based, inclusive, in byte // end .. end, zero-based, exclusive, in byte - public String GetString(int beg, int end) { + public String GetString(long beg, long end) { int len = 0; - char[] buf = new char[end - beg]; - int oldPos = getPos(); + char[] buf = new char[(int) (end - beg)]; + long oldPos = getPos(); setPos(beg); while (getPos() < end) buf[len++] = (char) Read(); setPos(oldPos); return new String(buf, 0, len); } - public int getPos() { + public long getPos() { return bufPos + bufStart; } - public void setPos(int value) { + public void setPos(long value) { if (value >= fileLen && stream != null) { // Wanted position is after buffer and the stream // is not seek-able e.g. network or console, @@ -168,7 +171,7 @@ public void setPos(int value) { } if (value >= bufStart && value < bufStart + bufLen) { // already in buffer - bufPos = value - bufStart; + bufPos = (int) (value - bufStart); } else if (file != null) { // must be swapped in try { file.seek(value); @@ -184,7 +187,7 @@ public void setPos(int value) { } } else { // set the position to the end of the file, Pos will return fileLen. - bufPos = fileLen - bufStart; + throw new InternalError(); } } @@ -294,8 +297,8 @@ public class Scanner { Token t; // current token int ch; // current input character - int pos; // byte position of current character - int charPos; // position by unicode characters starting with 0 + long pos; // byte position of current character + long charPos; // position by unicode characters starting with 0 int col; // column number of current character int line; // line number of current character int oldEols; // EOLs that appeared in a comment; @@ -328,52 +331,52 @@ public class Scanner { start.set(44, 8); start.set(34, 9); start.set(Buffer.EOF, -1); - literals.put("begin_compilation", new Integer(2)); - literals.put("name", new Integer(3)); - literals.put("method", new Integer(4)); - literals.put("date", new Integer(5)); - literals.put("end_compilation", new Integer(6)); - literals.put("begin_cfg", new Integer(7)); - literals.put("id", new Integer(8)); - literals.put("caller_id", new Integer(9)); - literals.put("end_cfg", new Integer(10)); - literals.put("begin_block", new Integer(11)); - literals.put("from_bci", new Integer(12)); - literals.put("to_bci", new Integer(13)); - literals.put("predecessors", new Integer(14)); - literals.put("successors", new Integer(15)); - literals.put("xhandlers", new Integer(16)); - literals.put("flags", new Integer(17)); - literals.put("dominator", new Integer(18)); - literals.put("loop_index", new Integer(19)); - literals.put("loop_depth", new Integer(20)); - literals.put("first_lir_id", new Integer(21)); - literals.put("last_lir_id", new Integer(22)); - literals.put("probability", new Integer(23)); - literals.put("end_block", new Integer(24)); - literals.put("begin_states", new Integer(25)); - literals.put("begin_stack", new Integer(26)); - literals.put("end_stack", new Integer(27)); - literals.put("begin_locks", new Integer(28)); - literals.put("end_locks", new Integer(29)); - literals.put("begin_locals", new Integer(30)); - literals.put("end_locals", new Integer(31)); - literals.put("end_states", new Integer(32)); - literals.put("size", new Integer(33)); - literals.put("begin_HIR", new Integer(36)); - literals.put("end_HIR", new Integer(37)); - literals.put("begin_LIR", new Integer(39)); - literals.put("end_LIR", new Integer(40)); - literals.put("begin_IR", new Integer(41)); - literals.put("HIR", new Integer(42)); - literals.put("LIR", new Integer(43)); - literals.put("end_IR", new Integer(44)); - literals.put("begin_intervals", new Integer(46)); - literals.put("end_intervals", new Integer(47)); - literals.put("begin_nmethod", new Integer(49)); - literals.put("end_nmethod", new Integer(50)); - literals.put("begin_bytecodes", new Integer(51)); - literals.put("end_bytecodes", new Integer(52)); + literals.put("begin_compilation", 2); + literals.put("name", 3); + literals.put("method", 4); + literals.put("date", 5); + literals.put("end_compilation", 6); + literals.put("begin_cfg", 7); + literals.put("id", 8); + literals.put("caller_id", 9); + literals.put("end_cfg", 10); + literals.put("begin_block", 11); + literals.put("from_bci", 12); + literals.put("to_bci", 13); + literals.put("predecessors", 14); + literals.put("successors", 15); + literals.put("xhandlers", 16); + literals.put("flags", 17); + literals.put("dominator", 18); + literals.put("loop_index", 19); + literals.put("loop_depth", 20); + literals.put("first_lir_id", 21); + literals.put("last_lir_id", 22); + literals.put("probability", 23); + literals.put("end_block", 24); + literals.put("begin_states", 25); + literals.put("begin_stack", 26); + literals.put("end_stack", 27); + literals.put("begin_locks", 28); + literals.put("end_locks", 29); + literals.put("begin_locals", 30); + literals.put("end_locals", 31); + literals.put("end_states", 32); + literals.put("size", 33); + literals.put("begin_HIR", 36); + literals.put("end_HIR", 37); + literals.put("begin_LIR", 39); + literals.put("end_LIR", 40); + literals.put("begin_IR", 41); + literals.put("HIR", 42); + literals.put("LIR", 43); + literals.put("end_IR", 44); + literals.put("begin_intervals", 46); + literals.put("end_intervals", 47); + literals.put("begin_nmethod", 49); + literals.put("end_nmethod", 50); + literals.put("begin_bytecodes", 51); + literals.put("end_bytecodes", 52); literalFirstChar = new boolean[0]; for (String literal : literals.keySet()) { @@ -464,7 +467,7 @@ Token NextToken() { ) NextCh(); int recKind = noSym; - int recEnd = pos; + long recEnd = pos; t = new Token(); t.pos = pos; t.col = col; t.line = line; t.charPos = charPos; int state = start.state(ch); @@ -475,7 +478,7 @@ Token NextToken() { case -1: { t.kind = eofSym; break loop; } // NextCh already done case 0: { if (recKind != noSym) { - tlen = recEnd - t.pos; + tlen = Math.toIntExact(recEnd - t.pos); SetScannerBehindT(); } t.kind = recKind; break loop; From 21dea8a4a1fd82596603be5b87a88f300038f737 Mon Sep 17 00:00:00 2001 From: Tom Rodriguez Date: Thu, 23 Oct 2025 10:10:37 -0700 Subject: [PATCH 04/11] Start with everything expanded --- .../visualizer/ir/model/IRTextBuilder.java | 22 +++++++++---------- .../visualizer/nc/model/NCTextBuilder.java | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRTextBuilder.java b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRTextBuilder.java index 978ac9ec435f..fe64a3dd0d69 100644 --- a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRTextBuilder.java +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRTextBuilder.java @@ -23,11 +23,6 @@ package at.ssw.visualizer.ir.model; import at.ssw.visualizer.ir.IREditorSupport; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Set; import at.ssw.visualizer.model.Compilation; import at.ssw.visualizer.model.cfg.BasicBlock; import at.ssw.visualizer.model.cfg.ControlFlowGraph; @@ -40,13 +35,18 @@ import at.ssw.visualizer.texteditor.model.Text; import at.ssw.visualizer.texteditor.model.TextBuilder; import at.ssw.visualizer.texteditor.model.TextRegion; -import java.text.DateFormat; -import java.util.Arrays; import org.netbeans.api.editor.fold.FoldType; import org.netbeans.editor.TokenID; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + /** - * * @author Christian Wimmer */ public class IRTextBuilder extends TextBuilder { @@ -239,7 +239,7 @@ private void appendBlock(ControlFlowGraph cfg, BasicBlock block) { // record foldings (no nested foldings if only one detail block is present) if (blockFoldings.size() > 0) { text.append(" \n"); - foldingRegions.add(new FoldingRegion(KIND_BLOCK, bodyStart, text.length() - 1, true)); + foldingRegions.add(new FoldingRegion(KIND_BLOCK, bodyStart, text.length() - 1, false)); if (blockFoldings.size() > 1) { for (FoldingRegion folding : blockFoldings) { foldingRegions.add(folding); @@ -389,7 +389,7 @@ protected void appendColumn(IRInstruction instruction, String[] columnNames, int } } if (foldStart != -1) { - foldingRegions.add(new FoldingRegion(KIND_MULTILINE, foldStart, text.length(), true)); + foldingRegions.add(new FoldingRegion(KIND_MULTILINE, foldStart, text.length(), false)); } text.append("\n"); } @@ -425,7 +425,7 @@ private FoldingRegion appendLir(BasicBlock block) { recordLir(block, instruction); } - return new FoldingRegion(KIND_LIR, start, text.length() - 1, true); + return new FoldingRegion(KIND_LIR, start, text.length() - 1, false); } private void recordLir(BasicBlock block, IRInstruction lir) { diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTextBuilder.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTextBuilder.java index 7949aabb1342..71c4b7b8e9a9 100644 --- a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTextBuilder.java +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTextBuilder.java @@ -96,7 +96,7 @@ private Text buildDocument(ControlFlowGraph cfg, boolean skipLIR) { } } if (codeBlock != null) { - foldingRegions.add(new FoldingRegion(KIND_BLOCK, codeBlock.codeStart + 1, codeBlock.start + codeBlock.length - 1, true)); + foldingRegions.add(new FoldingRegion(KIND_BLOCK, codeBlock.codeStart + 1, codeBlock.start + codeBlock.length - 1, false)); } return buildText(cfg, NCEditorSupport.MIME_TYPE); @@ -231,7 +231,7 @@ private void appendBlock() { private void checkComment() { if (commentBlock != null) { - foldingRegions.add(new FoldingRegion(LIR_BLOCK, commentBlock.start, commentBlock.start + commentBlock.length + 1, true)); + foldingRegions.add(new FoldingRegion(LIR_BLOCK, commentBlock.start, commentBlock.start + commentBlock.length + 1, false)); commentBlock = null; } } From 255cf1cacc78e4a3d047460f7ba07787667b94d5 Mon Sep 17 00:00:00 2001 From: Tom Rodriguez Date: Thu, 23 Oct 2025 10:05:42 -0700 Subject: [PATCH 05/11] Support opening CFG files in existing instance --- .../C1Visualizer/CompilationView/pom.xml | 5 + .../view/action/C1VisualizerDataObject.java | 100 ++++++++++++++++++ visualizer/C1Visualizer/application/pom.xml | 4 - .../impl/BinaryGraphDataObject.java | 1 - 4 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/C1VisualizerDataObject.java diff --git a/visualizer/C1Visualizer/CompilationView/pom.xml b/visualizer/C1Visualizer/CompilationView/pom.xml index 42f2b6658f47..4a364f026324 100644 --- a/visualizer/C1Visualizer/CompilationView/pom.xml +++ b/visualizer/C1Visualizer/CompilationView/pom.xml @@ -29,6 +29,11 @@ org-netbeans-modules-editor-mimelookup ${netbeans.version} + + org.netbeans.modules + org-netbeans-modules-editor-mimelookup-impl + ${netbeans.version} + org.netbeans.api org-netbeans-modules-sendopts diff --git a/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/C1VisualizerDataObject.java b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/C1VisualizerDataObject.java new file mode 100644 index 000000000000..37adb7d10508 --- /dev/null +++ b/visualizer/C1Visualizer/CompilationView/src/main/java/at/ssw/visualizer/compilation/view/action/C1VisualizerDataObject.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package at.ssw.visualizer.compilation.view.action; + +import at.ssw.visualizer.model.CompilationModel; +import org.netbeans.api.actions.Openable; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.cookies.OpenCookie; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.filesystems.MIMEResolver; +import org.openide.loaders.DataObject; +import org.openide.loaders.DataObjectExistsException; +import org.openide.loaders.MultiDataObject; +import org.openide.loaders.MultiFileLoader; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.RequestProcessor; + +import java.io.File; + +/** + * DataObject that represents a .CFG file. This object is NOT designed for presentation + * as is usual for NB dataobjects. It just serves to provide Open file from commandline + * feature, nothing more. + */ +@NbBundle.Messages( + "LBL_GraalVMC1VisualizerFile=GraalVM C1Visualizer File" +) +@MIMEResolver.ExtensionRegistration( + displayName = "#LBL_GraalVMC1VisualizerFile", + mimeType = C1VisualizerDataObject.GRAAL_CFG_MIME_TYPE, + position = 3232, + extension = {"cfg"} +) +@DataObject.Registration( + displayName = "LBL_GraalVMC1VisualizerFile", + mimeType = C1VisualizerDataObject.GRAAL_CFG_MIME_TYPE, + position = 400 +) +public class C1VisualizerDataObject extends MultiDataObject { + /** + * MIME type for CFG dumps + */ + public static final String GRAAL_CFG_MIME_TYPE = "application/x-c1visualizer"; // NOI18N + + public C1VisualizerDataObject(FileObject fo, MultiFileLoader loader) throws DataObjectExistsException { + super(fo, loader); + getCookieSet().assign(Openable.class, new OpenImpl()); + } + + private class OpenImpl implements OpenCookie { + @Override + public void open() { + importPrimaryFile(); + } + } + + public void importPrimaryFile() { + FileObject fo = getPrimaryFile(); + File f = FileUtil.toFile(fo); + if (f == null) { + return; + } + + final String fileName = f.getAbsolutePath(); + RequestProcessor.getDefault().post(new Runnable() { + public void run() { + CompilationModel model = Lookup.getDefault().lookup(CompilationModel.class); + String errorMsg = model.parseInputFile(fileName); + if (errorMsg != null) { + NotifyDescriptor d = new NotifyDescriptor.Message("Errors while parsing input:\n" + errorMsg, NotifyDescriptor.ERROR_MESSAGE); + DialogDisplayer.getDefault().notify(d); + } + } + }); + } +} + diff --git a/visualizer/C1Visualizer/application/pom.xml b/visualizer/C1Visualizer/application/pom.xml index 5da0a2f73c72..90b2da5429fa 100644 --- a/visualizer/C1Visualizer/application/pom.xml +++ b/visualizer/C1Visualizer/application/pom.xml @@ -775,10 +775,6 @@ org.netbeans.modules org-netbeans-modules-utilities-project - - org.netbeans.modules - org-netbeans-modules-utilities - org.netbeans.modules org-netbeans-modules-versioning-core diff --git a/visualizer/IdealGraphVisualizer/Coordinator/src/main/java/org/graalvm/visualizer/coordinator/impl/BinaryGraphDataObject.java b/visualizer/IdealGraphVisualizer/Coordinator/src/main/java/org/graalvm/visualizer/coordinator/impl/BinaryGraphDataObject.java index 904cc35beb12..c1342390b492 100644 --- a/visualizer/IdealGraphVisualizer/Coordinator/src/main/java/org/graalvm/visualizer/coordinator/impl/BinaryGraphDataObject.java +++ b/visualizer/IdealGraphVisualizer/Coordinator/src/main/java/org/graalvm/visualizer/coordinator/impl/BinaryGraphDataObject.java @@ -96,7 +96,6 @@ public void importPrimaryFile() { return; } try { - Thread.dumpStack(); FileImporter.asyncImportDocument(f.toPath(), true, true, null); } catch (IOException ex) { Exceptions.printStackTrace( From 3b0f8b26b8d940fcdc0de6c276281350de4f7672 Mon Sep 17 00:00:00 2001 From: Tom Rodriguez Date: Mon, 27 Oct 2025 20:24:54 -0700 Subject: [PATCH 06/11] Remove non-functional disassembler --- .../nc/model/HexCodeFileSupport.java | 30 - .../visualizer/nc/model/NCTextBuilder.java | 5 +- .../com/oracle/max/hcfdis/HexCodeFileDis.java | 529 ------------------ 3 files changed, 4 insertions(+), 560 deletions(-) delete mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/HexCodeFileSupport.java delete mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/com/oracle/max/hcfdis/HexCodeFileDis.java diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/HexCodeFileSupport.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/HexCodeFileSupport.java deleted file mode 100644 index 6595d0099cfd..000000000000 --- a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/HexCodeFileSupport.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package at.ssw.visualizer.nc.model; - - -public class HexCodeFileSupport { - public static String decode(String text) { - return com.oracle.max.hcfdis.HexCodeFileDis.processEmbeddedString(text); - } -} diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTextBuilder.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTextBuilder.java index 71c4b7b8e9a9..163af68641ef 100644 --- a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTextBuilder.java +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCTextBuilder.java @@ -37,6 +37,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; + import org.netbeans.api.editor.fold.FoldType; import org.netbeans.editor.TokenID; @@ -69,7 +70,9 @@ public Text buildDocument(ControlFlowGraph cfg) { private Text buildDocument(ControlFlowGraph cfg, boolean skipLIR) { String text = cfg.getNativeMethod().getMethodText().trim(); if (text.startsWith("<<>>")) { - text = HexCodeFileSupport.decode(text); + // There is no builtin disassembler so just pass it through for now + append(text); + return buildText(cfg, NCEditorSupport.MIME_TYPE); } String[] methodText = text.split("\n"); diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/com/oracle/max/hcfdis/HexCodeFileDis.java b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/com/oracle/max/hcfdis/HexCodeFileDis.java deleted file mode 100644 index 4576c14ad0db..000000000000 --- a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/com/oracle/max/hcfdis/HexCodeFileDis.java +++ /dev/null @@ -1,529 +0,0 @@ -/* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -/* - * Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved. - * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. - */ -package com.oracle.max.hcfdis; - -import static com.oracle.max.hcfdis.HexCodeFile.EMBEDDED_HCF_CLOSE; -import static com.oracle.max.hcfdis.HexCodeFile.EMBEDDED_HCF_OPEN; -import static com.oracle.max.hcfdis.HexCodeFileDis.Width.ONE; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Formatter; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -import com.oracle.max.hcfdis.HexCodeFile.JumpTable; -import com.oracle.max.hcfdis.HexCodeFile.JumpTable.EntryFormat; - -import capstone.Capstone; - -/** - * Utility for converting a {@link HexCodeFile} to a commented disassembly. - * - * The fully qualified name of {@link #processEmbeddedString(String)} must not change as it's called - * directly by C1Visualizer. - * - * @see "https://ol-bitbucket.us.oracle.com/projects/G/repos/c1visualizer/browse/C1Visualizer/Native%20Code%20Editor/src/at/ssw/visualizer/nc/model/HexCodeFileSupport.java#6" - */ -public class HexCodeFileDis { - - public HexCodeFileDis() { - } - - /** - * Decoding method called by external tools via reflection. - */ - public static String processEmbeddedString(String source) { - if (!source.startsWith(EMBEDDED_HCF_OPEN) || !source.endsWith(EMBEDDED_HCF_CLOSE)) { - throw new IllegalArgumentException("Input string is not in embedded format"); - } - String input = source.substring(EMBEDDED_HCF_OPEN.length(), source.length() - EMBEDDED_HCF_CLOSE.length()); - HexCodeFile hcf = HexCodeFile.parse(input, 0, input, ""); - if (hcf == null) { - throw new InternalError("Malformed HexCodeFile embedded in string"); - } - - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - process(hcf, new PrintStream(buf), true); - return buf.toString(); - } - - /** - * Disassembles all HexCodeFiles embedded in a given input string. - * - * @param input some input containing 0 or more HexCodeFiles - * @param inputName name for the input source to be used in error messages - * @param startDelim the delimiter just before to an embedded HexCodeFile in {@code input} - * @param endDelim the delimiter just after to an embedded HexCodeFile in {@code input} - * @return the value of {@code input} with all embedded HexCodeFiles converted to their - * disassembled form - */ - public static String processAll(String input, String inputName, String startDelim, String endDelim, boolean showComments) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(input.length() * 2); - PrintStream out = new PrintStream(baos); - int hcfCount = 0; - - int codeEnd = 0; - int index; - while ((index = input.indexOf(startDelim, codeEnd)) != -1) { - int codeStart = index + startDelim.length(); - - String copy = input.substring(codeEnd, codeStart); - if (copy.startsWith(endDelim)) { - copy = copy.substring(endDelim.length()); - } - if (copy.endsWith(startDelim)) { - copy = copy.substring(0, copy.length() - startDelim.length()); - } - out.println(copy); - - int endIndex = input.indexOf(endDelim, codeStart); - assert endIndex != -1; - - String source = input.substring(codeStart, endIndex); - - HexCodeFile hcf = HexCodeFile.parse(input, codeStart, source, inputName); - if (hcf == null) { - throw new InternalError("Malformed HexCodeFile in " + inputName); - } - process(hcf, out, showComments); - hcfCount++; - codeEnd = endIndex; - } - - String copy = input.substring(codeEnd); - if (copy.startsWith(endDelim)) { - copy = copy.substring(endDelim.length()); - } - - System.out.println(inputName + ": disassembled " + hcfCount + " embedded HexCodeFiles"); - out.print(copy); - out.flush(); - return baos.toString(); - } - - /** - * Computes the max width of a column based on values to be be printed in the column. - */ - static class Width { - static final Width ONE = new Width(1); - - Width(int initialValue) { - this.value = initialValue; - } - - void update(int i, int radix) { - assert this != ONE; - update(Integer.toString(i, radix)); - } - - void update(String s) { - assert this != ONE; - int len = s.length(); - if (len > value) { - value = len; - } - } - - @Override - public String toString() { - return String.valueOf(value); - } - - int value; - } - - /** - * An entry in a jump table in the decoded instruction stream. - */ - static class JumpTableEntry { - - JumpTableEntry(int offset, long address, int primaryKey, Integer secondaryKey, long target) { - this.offset = offset; - this.address = address; - this.primaryKey = primaryKey; - this.secondaryKey = secondaryKey; - this.target = target; - } - - final int offset; - final long address; - final int primaryKey; - final Integer secondaryKey; - - /** - * Initially a {@code long}, replaced with an {@link Instruction} by - * {@link HexCodeFileDis#updateTargets}. - */ - Object target; - - @Override - public String toString() { - return toString(ONE, ONE, ONE, ONE); - } - - String toString(Width offsetWidth, Width labelWidth, Width primaryKeyWidth, Width secondaryKeyWidth) { - String secondaryKeyString = secondaryKey == null ? "" : String.format(", %-" + secondaryKeyWidth + "s", secondaryKey); - String targetString; - if (target instanceof Instruction) { - Instruction targetInstruction = (Instruction) target; - targetString = targetInstruction.labeledAddress(); - } else { - targetString = String.valueOf(target); - } - return String.format("0x%08x(+0x%-" + offsetWidth + "x): %" + labelWidth + "s .case %-" + primaryKeyWidth + "d%s -> %s", - address, - offset, - "", - primaryKey, - secondaryKeyString, - targetString); - } - } - - /** - * A decoded instruction. - */ - static class Instruction { - Instruction(int offset, Capstone.CsInsn insn, String operandComment, List comments) { - this.offset = offset; - this.insn = insn; - this.operandComment = operandComment; - this.comments = comments; - } - - final int offset; - final Capstone.CsInsn insn; - final String operandComment; - final List comments; - - /** - * The entries of the jump table after this instruction. - */ - final List jumpTable = new ArrayList<>(); - - /** - * The label of this instruction if it is targeted by a jump instruction. Otherwise, - * {@code -1}. - */ - int label = -1; - - /** - * The instruction targeted by this instruction if it is a jump. - */ - Instruction target; - - String labeledAddress() { - return String.format("L%d: 0x%x", label, insn.address); - } - - @Override - public String toString() { - return toString(ONE, ONE, true); - } - - String toString(Width offsetWidth, Width labelWidth, boolean showComments) { - Formatter buf = new Formatter(); - String l = label == -1 ? "" : "L" + label + ":"; - if (showComments && comments != null) { - for (String comment : comments) { - final String commentLinePrefix = ";; "; - buf.format("%s%s%n", commentLinePrefix, comment.replace("\n", "\n" + commentLinePrefix)); - } - } - Object operand = target == null ? insn.opStr : target.labeledAddress(); - buf.format("0x%08x(+0x%-" + offsetWidth + "x): %" + labelWidth + "s %s\t%s%s", insn.address, offset, l, insn.mnemonic, operand, showComments ? operandComment : ""); - Width primaryKeyWidth = new Width(1); - Width secondaryKeyWidth = new Width(1); - for (JumpTableEntry c : jumpTable) { - primaryKeyWidth.update(c.primaryKey, 10); - if (c.secondaryKey != null) { - secondaryKeyWidth.update(c.secondaryKey, 10); - } - } - for (JumpTableEntry c : jumpTable) { - buf.format("\n%s", c.toString(offsetWidth, labelWidth, primaryKeyWidth, secondaryKeyWidth)); - } - return buf.toString(); - } - } - - /** - * Disassembles a given HexCodeFile. - * - * @param out where the HexCodeFile disassembly is printed - */ - public static void process(HexCodeFile hcf, PrintStream out, boolean showComments) { - Capstone cs; - boolean littleEndian; - - switch (hcf.isa.toLowerCase()) { - case "amd64": - littleEndian = true; - cs = new Capstone(Capstone.CS_ARCH_X86, Capstone.CS_MODE_64); - break; - case "aarch64": - littleEndian = true; - cs = new Capstone(Capstone.CS_ARCH_ARM64, Capstone.CS_MODE_ARM); - break; - default: - throw new IllegalArgumentException("Unexpected ISA: " + hcf.isa); - } - - // Ordered map from offset to decoded instructions - Map instructionMap = new LinkedHashMap<>(); - - if (hcf.jumpTables.size() == 0) { - for (Instruction i : disassemble(hcf, cs, 0, hcf.code)) { - instructionMap.put(i.offset, i); - } - } else { - // Break the disassembly into chunks separated by jump tables - int startOffset = 0; - for (JumpTable jumpTable : hcf.jumpTables) { - int jumpTableOffset = jumpTable.getPosition(); - List insns = disassembleAtOffset(hcf, cs, startOffset, jumpTableOffset); - for (Instruction i : insns) { - instructionMap.put(i.offset, i); - } - EntryFormat entryFormat = jumpTable.entryFormat; - int entrySize = entryFormat.size; - Instruction lastInsn = insns.get(insns.size() - 1); - long entries = Math.abs(jumpTable.high - (long) jumpTable.low); - for (int i = 0; i <= entries; i++) { - int entryOffset = jumpTable.getPosition() + i * entrySize; - Integer secondaryKey; - int targetOffsetRelativeToJumpTable; - switch (jumpTable.entryFormat) { - case OFFSET: - secondaryKey = null; - targetOffsetRelativeToJumpTable = readInt(littleEndian, hcf.code, entryOffset); - break; - case KEY2_OFFSET: - secondaryKey = readInt(littleEndian, hcf.code, entryOffset); - targetOffsetRelativeToJumpTable = readInt(littleEndian, hcf.code, entryOffset + 4); - break; - default: - throw new IllegalArgumentException("Unknown jump table entry format: " + jumpTable.entryFormat); - } - int primaryKey = jumpTable.low + i; - long target = hcf.startAddress + jumpTableOffset + targetOffsetRelativeToJumpTable; - lastInsn.jumpTable.add(new JumpTableEntry(entryOffset, hcf.startAddress + entryOffset, primaryKey, secondaryKey, target)); - - } - long newStartOffset = jumpTableOffset + entrySize * (jumpTable.high - (long) jumpTable.low + 1); - startOffset = (int) newStartOffset; - assert startOffset == newStartOffset; - } - if (startOffset < hcf.code.length) { - for (Instruction i : disassembleAtOffset(hcf, cs, startOffset, hcf.code.length)) { - instructionMap.put(i.offset, i); - } - } - } - - Width labelWidth = new Width(1); - Width offsetWidth = new Width(1); - - Instruction[] instructions = instructionMap.values().toArray(new Instruction[0]); - if (instructions.length > 0) { - long firstInsnAddress = instructions[0].insn.address; - long lastInsnAddress = instructions[instructions.length - 1].insn.address; - Map targets = new HashMap<>(); - for (Instruction i : instructions) { - offsetWidth.update(Integer.toHexString(i.offset)); - updateTargets(instructionMap, targets, labelWidth, firstInsnAddress, lastInsnAddress, i); - } - - // Increase max label width to account for "L:" characters. - labelWidth.value += 2; - - for (Instruction i : instructionMap.values()) { - out.println(i.toString(offsetWidth, labelWidth, showComments)); - } - } - out.flush(); - } - - private static void updateTargets(Map instructionMap, - Map targets, - Width labelWidth, - long firstInsnAddress, - long lastInsnAddress, - Instruction i) { - String opStr = i.insn.opStr; - if (opStr != null) { - opStr = opStr.toLowerCase(); - if (opStr.startsWith("0x")) { - opStr = opStr.substring(2); - } - if (opStr.length() != 0) { - char ch = opStr.charAt(0); - if ((ch >= 'a' && ch <= 'f') || (ch >= '0' && ch <= '9')) { - try { - i.target = findTarget(instructionMap, targets, labelWidth, firstInsnAddress, lastInsnAddress, Long.parseLong(opStr, 16)); - } catch (NumberFormatException e) { - } - } - } - } - for (JumpTableEntry e : i.jumpTable) { - e.target = findTarget(instructionMap, targets, labelWidth, firstInsnAddress, lastInsnAddress, (long) e.target); - } - } - - private static Instruction findTarget(Map instructionMap, Map targets, Width labelWidth, long firstInsnAddress, long lastInsnAddress, long address) { - if (address >= firstInsnAddress && address <= lastInsnAddress) { - assert address - firstInsnAddress < Integer.MAX_VALUE; - int offset = (int) (address - firstInsnAddress); - Instruction targetInstruction = instructionMap.get(offset); - if (targetInstruction != null) { - assert targetInstruction.insn.address == address; - int label = targetInstruction.label; - if (label == -1) { - targetInstruction.label = label = targets.size(); - } - labelWidth.update(Integer.toString(label)); - return targets.computeIfAbsent(address, a -> targetInstruction); - } - } - return null; - } - - private static int readInt(boolean littleEndian, byte[] code, int offset) { - if (littleEndian) { - return code[offset + 0] & 0xFF | - (code[offset + 1] & 0xFF) << 8 | - (code[offset + 2] & 0xFF) << 16 | - (code[offset + 3] & 0xFF) << 24; - } - return code[offset + 3] & 0xFF | - (code[offset + 2] & 0xFF) << 8 | - (code[offset + 1] & 0xFF) << 16 | - (code[offset + 0] & 0xFF) << 24; - } - - private static List disassembleAtOffset(HexCodeFile hcf, Capstone cs, int startOffset, int endOffset) { - byte[] section = Arrays.copyOfRange(hcf.code, startOffset, endOffset); - return disassemble(hcf, cs, startOffset, section); - } - - private static List disassemble(HexCodeFile hcf, Capstone cs, int startOffset, byte[] section) { - Capstone.CsInsn[] allInsn = cs.disasm(section, hcf.startAddress + startOffset); - List instructions = new ArrayList<>(allInsn.length); - for (Capstone.CsInsn insn : allInsn) { - int insnOffset = (int) (insn.address - hcf.startAddress); - List comments = hcf.comments.get(insnOffset); - StringBuilder operandComments = null; - for (int i = 0; i < insn.size; i++) { - List list = hcf.operandComments.get(insnOffset + i); - if (list != null) { - for (String c : list) { - if (operandComments == null) { - operandComments = new StringBuilder("\t"); - } else { - operandComments.append(" "); - } - operandComments.append(c); - } - } - } - Instruction instr = new Instruction(insnOffset, insn, operandComments != null ? operandComments.toString() : "", comments); - instructions.add(instr); - } - return instructions; - } - - public static void main(String[] args) throws IOException { - String dirOption = null; - int firstArg = 0; - - for (int i = 0; i < args.length; i++) { - if (args[i].startsWith("-d=")) { - dirOption = args[i].substring(3); - } else if (args[i].startsWith("-")) { - usage(args[i].equals("-h") ? null : "Unexpected option: " + args[i]); - } else { - firstArg = i; - break; - } - } - - File outDir = null; - if (dirOption != null) { - outDir = new File(dirOption); - if (!outDir.isDirectory()) { - if (!outDir.mkdirs()) { - throw new Error("Could not create output directory " + outDir.getAbsolutePath()); - } - } - } - - for (int i = firstArg; i < args.length; i++) { - String arg = args[i]; - Path inputFile = Paths.get(arg); - StringBuilder sb = new StringBuilder(); - try (Stream stream = Files.lines(inputFile)) { - stream.forEach(s -> sb.append(s).append("\n")); - } - String input = sb.toString(); - - String inputSource = inputFile.toAbsolutePath().toString(); - String output = processAll(input, inputSource, EMBEDDED_HCF_OPEN, EMBEDDED_HCF_CLOSE, true); - - Path outputFile; - if (outDir == null) { - outputFile = inputFile; - } else { - outputFile = Paths.get(outDir.toString(), inputFile.toFile().getName()); - } - - if (!outputFile.equals(inputFile) || !output.equals(input)) { - Files.write(outputFile, output.getBytes(StandardCharsets.UTF_8)); - } - } - } - - private static void usage(String s) { - if (s != null) { - System.err.println(s); - } - System.err.println("Usage: hcfdis [ -d=dir ] file [ files ]"); - System.exit(s == null ? 0 : -1); - } -} From a5f65f921de18a6cbd3f73bdbb3a81275ea637ae Mon Sep 17 00:00:00 2001 From: Tom Rodriguez Date: Mon, 27 Oct 2025 21:29:06 -0700 Subject: [PATCH 07/11] Build changes --- .gitignore | 1 + compiler/mx.compiler/mx_graal_tools.py | 41 ++-- .../java/at/ssw/visualizer/bc/model/BCExample | 18 -- .../resources/at/ssw/visualizer/bc/layer.xml | 8 - .../ssw/visualizer/bc/options/BC-classpaths | 0 .../at/ssw/visualizer/bc/model/layer.xml | 1 - .../java/at/ssw/visualizer/ir/model/IRExample | 13 -- .../resources/at/ssw/visualizer/ir/layer.xml | 8 - .../java/at/ssw/visualizer/nc/model/NCExample | 9 - .../resources/at/ssw/visualizer/nc/layer.xml | 7 - .../java/at/ssw/visualizer/DefaultExample | 1 - .../resources/at/ssw/visualizer/layer.xml | 3 - visualizer/C1Visualizer/application/pom.xml | 59 ++++++ .../src/main/resources/bin/c1visualizer | 199 ++++++++++++++++++ .../src/main/resources/c1visualizer.conf | 2 +- .../src/main/resources/c1visualizer.icns | Bin 0 -> 1041741 bytes visualizer/C1Visualizer/branding.jnlp | 15 -- visualizer/C1Visualizer/master.jnlp | 25 --- visualizer/mx.visualizer/mx_visualizer.py | 69 +++--- visualizer/mx.visualizer/suite.py | 16 +- 20 files changed, 334 insertions(+), 161 deletions(-) delete mode 100644 visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCExample delete mode 100644 visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BC-classpaths delete mode 100644 visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRExample delete mode 100644 visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCExample delete mode 100644 visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/DefaultExample create mode 100755 visualizer/C1Visualizer/application/src/main/resources/bin/c1visualizer create mode 100644 visualizer/C1Visualizer/application/src/main/resources/c1visualizer.icns delete mode 100644 visualizer/C1Visualizer/branding.jnlp delete mode 100644 visualizer/C1Visualizer/master.jnlp diff --git a/.gitignore b/.gitignore index 28321b0496eb..1433d695556d 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ bench-results.json jmh_result.json /vm/src/installer/dist/ visualizer/IdealGraphVisualizer/*/target/ +visualizer/C1Visualizer/*/target/ /.src-rev *.interp *.tokens diff --git a/compiler/mx.compiler/mx_graal_tools.py b/compiler/mx.compiler/mx_graal_tools.py index e1c1d355100c..192b72a9e238 100644 --- a/compiler/mx.compiler/mx_graal_tools.py +++ b/compiler/mx.compiler/mx_graal_tools.py @@ -79,8 +79,9 @@ def run_netbeans_app(app_name, jdkhome, args=None, dist=None): print('Consider flag -J-Xms4g -J-Xmx8g if dealing with large graphs') mx.run(launch+args) -def igv(args): - """run the Ideal Graph Visualizer + +def netbeans_docstring(fullname, mixedname, mxname): + return f"""run the {fullname} The current version is based on NetBeans 26 which officially supports JDK 17 through JDK 24. A supported JDK will be chosen from the JDKs known to mx but it will fall back to whatever is @@ -90,19 +91,21 @@ def igv(args): You can directly control which JDK is used to launch IGV using - mx igv --jdkhome /path/to/java/home + mx {mxname} --jdkhome /path/to/java/home This will completely ignore any JAVA_HOME settings in mx. - Extra NetBeans specific options can be passed as well. mx igv --help will show the + Extra NetBeans specific options can be passed as well. mx {mxname} --help will show the help for the NetBeans launcher. """ + +def launch_netbeans_app(fullname, mixedname, mxname, args): min_version = 17 max_version = 24 min_version_spec = mx.VersionSpec(str(min_version)) next_version_spec = mx.VersionSpec(str(max_version + 1)) - def _igvJdkVersionCheck(version): + def _netbeansJdkVersionCheck(version): return min_version_spec <= version < next_version_spec jdkhome = None @@ -111,29 +114,31 @@ def _do_not_abort(msg): pass # try to find a fully supported version first - jdk = mx.get_tools_jdk(versionCheck=_igvJdkVersionCheck, versionDescription=f'IGV prefers JDK {min_version} through JDK {max_version}', abortCallback=_do_not_abort) + jdk = mx.get_tools_jdk(versionCheck=_netbeansJdkVersionCheck, versionDescription=f'{fullname} prefers JDK {min_version} through JDK {max_version}', abortCallback=_do_not_abort) if jdk is None: # try any JDK jdk = mx.get_jdk() if jdk: jdkhome = jdk.home - mx.log(f'Launching IGV with {jdkhome}') - if not _igvJdkVersionCheck(jdk.version): - mx.warn(f'{jdk.home} is not an officially supported JDK for IGV.') + mx.log(f'Launching {fullname} with {jdkhome}') + if not _netbeansJdkVersionCheck(jdk.version): + mx.warn(f'{jdk.home} is not an officially supported JDK.') mx.warn(f'If you experience any problems try to use an LTS release between JDK {min_version} and JDK {max_version} instead.') - mx.warn(f'mx help igv provides more details.') + mx.warn(f'mx help {mxname} provides more details.') + + run_netbeans_app(mixedname, jdkhome, args=args, dist=f'{mixedname.upper()}_DIST') - run_netbeans_app('IdealGraphVisualizer', jdkhome, args=args, dist='IDEALGRAPHVISUALIZER_DIST') +def igv(args): + launch_netbeans_app('Ideal Graph Visualizer', 'IdealGraphVisualizer', 'igv', args) + +igv.__doc__ = netbeans_docstring('Ideal Graph Visualizer', 'IdealGraphVisualizer', 'igv') def c1visualizer(args): - """run the C1 Compiler Visualizer""" - v8u40 = mx.VersionSpec("1.8.0_40") - v12 = mx.VersionSpec("12") - def _c1vJdkVersionCheck(version): - return v8u40 <= version < v12 - jdkhome = mx.get_jdk(_c1vJdkVersionCheck, versionDescription='(JDK that is >= 1.8.0u40 and <= 11 which can be specified via EXTRA_JAVA_HOMES or --extra-java-homes)', purpose="running C1 Visualizer").home - run_netbeans_app('C1Visualizer', jdkhome, args() if callable(args) else args) + launch_netbeans_app('C1 Visualizer', 'C1Visualizer', 'c1visualizer', args) + +c1visualizer.__doc__ = netbeans_docstring('C1 Visualizer', 'C1Visualizer', 'c1visualizer') + def hsdis(args, copyToDir=None): """download the hsdis library and copy it to a specific dir or to the current JDK diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCExample b/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCExample deleted file mode 100644 index 3d5acf4953bd..000000000000 --- a/visualizer/C1Visualizer/BytecodeEditor/src/main/java/at/ssw/visualizer/bc/model/BCExample +++ /dev/null @@ -1,18 +0,0 @@ -Code(max_stack = 3, max_locals = 6, code_length = 60) - -B0 -> B1,B2 std -0: aload_0 -1: getfield java.lang.String.hash I (349) -4: istore_1 -5: iload_1 -6: ifne #58 -B3 <- B1,B4 -> B4,B5 plh -28: iload %5 -30: iload %4 -32: if_icmpge #53 -B4 <- B3 -> B3 -41: iinc %2 1 - - -Attribute(s) = -LineNumber(0, 1483), LineNumber(5, 1484), LineNumber(9, 1485), LineNumber(14, 1486) diff --git a/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/layer.xml b/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/layer.xml index 16f1504bba06..6ac977f8e289 100644 --- a/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/layer.xml +++ b/visualizer/C1Visualizer/BytecodeEditor/src/main/resources/at/ssw/visualizer/bc/layer.xml @@ -23,14 +23,6 @@ - - - - - - - - diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BC-classpaths b/visualizer/C1Visualizer/BytecodeModel/src/main/java/at/ssw/visualizer/bc/options/BC-classpaths deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/model/layer.xml b/visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/model/layer.xml index 7eea191dbefc..98fb647182a8 100644 --- a/visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/model/layer.xml +++ b/visualizer/C1Visualizer/BytecodeModel/src/main/resources/at/ssw/visualizer/bc/model/layer.xml @@ -10,6 +10,5 @@ - diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRExample b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRExample deleted file mode 100644 index 502547435938..000000000000 --- a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/java/at/ssw/visualizer/ir/model/IRExample +++ /dev/null @@ -1,13 +0,0 @@ -B0 <- B6 -> B7,B1 dom B6 [0, 6] std - Locals size 6 [virtual jint java.lang.String.hashCode()] - 0 a6 - __bci__use__tid__result___instr___________________________ (HIR) - . 1 4 i7 [R58|I] a6._20 (I) - 6 2 i8 0 - . 6 0 v9 if i7 != i8 then B7 else B1 - __nr___instr______________________________________________ (LIR) - 8 label [label:0x2d692b4] - 10 move [Base:[R57|L] Disp: 20|I] [R58|I] - 12 cmp [R58|I] [int:0|I] - 14 branch [NE] [B7] - 16 branch [AL] [B1] diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/layer.xml b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/layer.xml index c8ccde29f838..fb24a66e676c 100644 --- a/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/layer.xml +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/src/main/resources/at/ssw/visualizer/ir/layer.xml @@ -31,14 +31,6 @@ - - - - - - - - diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCExample b/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCExample deleted file mode 100644 index 7050d356c549..000000000000 --- a/visualizer/C1Visualizer/NativeCodeEditor/src/main/java/at/ssw/visualizer/nc/model/NCExample +++ /dev/null @@ -1,9 +0,0 @@ -[Entry Point] -B0 <- B6 -> B2,B1 dom B6 [0, 6] std - ;; 8 label [label:0x9ea954] - ;; 10 move [Base:[ecx|L] Disp: 20|I] [eax|I] - 0x00b0087f: movl 0x14(%ecx),%eax - ;; 12 cmp [eax|I] [int:0|I] - 0x00b00882: cmpl $0x0,%eax - ;; 14 branch [NE] [B2] - 0x00b00885: jne 0xb008ce diff --git a/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/layer.xml b/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/layer.xml index 80b080ee8cf9..579e1b5cb97e 100644 --- a/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/layer.xml +++ b/visualizer/C1Visualizer/NativeCodeEditor/src/main/resources/at/ssw/visualizer/nc/layer.xml @@ -12,13 +12,6 @@ - - - - - - - diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/DefaultExample b/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/DefaultExample deleted file mode 100644 index 47be9ba306c9..000000000000 --- a/visualizer/C1Visualizer/VisualizerUI/src/main/java/at/ssw/visualizer/DefaultExample +++ /dev/null @@ -1 +0,0 @@ -Default Text diff --git a/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/layer.xml b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/layer.xml index 1be5a1c51cc0..16376f194a68 100644 --- a/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/layer.xml +++ b/visualizer/C1Visualizer/VisualizerUI/src/main/resources/at/ssw/visualizer/layer.xml @@ -135,9 +135,6 @@ - - - diff --git a/visualizer/C1Visualizer/application/pom.xml b/visualizer/C1Visualizer/application/pom.xml index 90b2da5429fa..e65810f06a83 100644 --- a/visualizer/C1Visualizer/application/pom.xml +++ b/visualizer/C1Visualizer/application/pom.xml @@ -1168,7 +1168,66 @@ src/main/resources/${brandingToken}.conf src/main/resources/${brandingToken}.clusters + src/main/resources/bin + + + default-standalone-zip + + standalone-zip + + + ${brandingToken}-${project.version} + + + + + + maven-resources-plugin + 2.6 + + + install-mac-icon + package + + copy-resources + + + ${basedir}/target/${brandingToken} + UTF-8 + + + ${basedir}/src/main/resources + + ${brandingToken}.icns + + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.6 + + + zip-artifacts + package + + run + + + + + + + + + + diff --git a/visualizer/C1Visualizer/application/src/main/resources/bin/c1visualizer b/visualizer/C1Visualizer/application/src/main/resources/bin/c1visualizer new file mode 100755 index 000000000000..8bd327ef04b5 --- /dev/null +++ b/visualizer/C1Visualizer/application/src/main/resources/bin/c1visualizer @@ -0,0 +1,199 @@ +#!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# +# resolve symlinks +# + +PRG=$0 + +while [ -h "$PRG" ]; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '^.*-> \(.*\)$' 2>/dev/null` + if expr "$link" : '^/' 2> /dev/null >/dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi +done + +progdir=`dirname "$PRG"` +APPNAME=`basename "$PRG"` +if [ -z "$APP_DOCK_NAME" ] ; then + APP_DOCK_NAME="$APPNAME" +fi + +case "`uname`" in + Darwin*) + # set default userdir and cachedir on Mac OS X + DEFAULT_USERDIR_ROOT="${HOME}/Library/Application Support/${APPNAME}" + DEFAULT_CACHEDIR_ROOT=${HOME}/Library/Caches/${APPNAME} + ;; + *) + # set default userdir and cachedir on unix systems + DEFAULT_USERDIR_ROOT=${HOME}/.${APPNAME} + DEFAULT_CACHEDIR_ROOT=${HOME}/.cache/${APPNAME} + ;; +esac + +if [ -f "$progdir/../etc/$APPNAME".conf ] ; then + . "$progdir/../etc/$APPNAME".conf +fi + +# XXX does not correctly deal with spaces in non-userdir params +args="" + +case "`uname`" in + Darwin*) + if [ ! -z "$default_mac_userdir" ]; then + userdir="${default_mac_userdir}" + else + userdir="${default_userdir}" + fi + ;; + *) + userdir="${default_userdir}" + ;; +esac +while [ $# -gt 0 ] ; do + case "$1" in + --userdir) shift; if [ $# -gt 0 ] ; then userdir="$1"; fi + ;; + *) + if [[ "$1" == *\$* ]]; then + echo "Error: Argument '$1' contains a \$ character which cannot be safely handled the launcher script" >&2 + exit 1 + fi + args="$args \"$1\"" + ;; + esac + shift +done + +cachedir="${default_cachedir}" + +if [ -f "${userdir}/etc/$APPNAME".conf ] ; then + . "${userdir}/etc/$APPNAME".conf +fi + +if [ -n "$jdkhome" -a \! -d "$jdkhome" -a -d "$progdir/../$jdkhome" ]; then + # #74333: permit jdkhome to be defined as relative to app dir + jdkhome="$progdir/../$jdkhome" +fi + +readClusters() { + if [ -x /usr/ucb/echo ]; then + echo=/usr/ucb/echo + else + echo=echo + fi + while read X; do + if [ "$X" \!= "" ]; then + $echo "$progdir/../$X" + fi + done +} + +absolutize_paths() { + while read path; do + if [ -d "$path" ]; then + (cd "$path" 2>/dev/null && pwd) + else + echo "$path" + fi + done +} + +clusters=`(cat "$progdir/../etc/$APPNAME".clusters; echo) | readClusters | absolutize_paths | tr '\012' ':'` + +if [ ! -z "$extra_clusters" ] ; then + clusters="$clusters:$extra_clusters" +fi + +nbexec=`echo "$progdir"/../platform*/lib/nbexec` + +case "`uname`" in + Darwin*) + eval exec sh '"$nbexec"' \ + --jdkhome '"$jdkhome"' \ + -J-Xdock:name='"$APP_DOCK_NAME"' \ + '"-J-Xdock:icon=$progdir/../$APPNAME.icns"' \ + --clusters '"$clusters"' \ + --userdir '"${userdir}"' \ + --cachedir '"${cachedir}"' \ + ${default_options} \ + "$args" + ;; + *) + sh=sh + # #73162: Ubuntu uses the ancient Bourne shell, which does not implement trap well. + if [ -x /bin/bash ] + then + sh=/bin/bash + fi + + # See longer comments in nb/ide.launcher/unix/netbeans. + if [ "`command xrdb -query 2> /dev/null | grep Xft.dpi | cut -d ':' -f2 | xargs`" = 192 ] + then + echo "Detected 2x HiDPI scaling in Xft.dpi setting; setting GDK_SCALE=2" + export GDK_SCALE=2 + fi + if [ "`command xdpyinfo 2> /dev/null | grep 'resolution:.*dots per inch' | cut -d ':' -f2 | cut -d 'x' -f1 | sort -u | xargs`" = 192 ] + then + echo "Detected 192 DPI on all screens in xdpyinfo; setting GDK_SCALE=2" + export GDK_SCALE=2 + fi + + extra_automatic_options="" + + # See longer comments in nb/ide.launcher/unix/netbeans. + if [ ! -z "$KDE_FULL_SESSION" ] ; then + case "`command xrdb -query 2> /dev/null | grep Xft.rgba | cut -d ':' -f2 | xargs`" in + rgb) + extra_automatic_options="-J-Dawt.useSystemAAFontSettings=lcd_hrgb" + ;; + bgr) + extra_automatic_options="-J-Dawt.useSystemAAFontSettings=lcd_hbgr" + ;; + vrgb) + extra_automatic_options="-J-Dawt.useSystemAAFontSettings=lcd_vrgb" + ;; + vbgr) + extra_automatic_options="-J-Dawt.useSystemAAFontSettings=lcd_vbgr" + ;; + *) + extra_automatic_options="-J-Dawt.useSystemAAFontSettings=on" + ;; + esac + echo "Detected KDE; use explicit setting for font antialiasing ($extra_automatic_options)" + fi + + # Add extra_automatic_options before default_options, to allow system + # property definitions from the configuration file to take precedence. + eval exec $sh '"$nbexec"' \ + --jdkhome '"$jdkhome"' \ + --clusters '"$clusters"' \ + --userdir '"${userdir}"' \ + --cachedir '"${cachedir}"' \ + ${extra_automatic_options} \ + ${default_options} \ + "$args" + exit 1 + ;; +esac diff --git a/visualizer/C1Visualizer/application/src/main/resources/c1visualizer.conf b/visualizer/C1Visualizer/application/src/main/resources/c1visualizer.conf index 9acc1953512c..ed65b83298bc 100644 --- a/visualizer/C1Visualizer/application/src/main/resources/c1visualizer.conf +++ b/visualizer/C1Visualizer/application/src/main/resources/c1visualizer.conf @@ -26,7 +26,7 @@ default_cachedir="${DEFAULT_CACHEDIR_ROOT}/0.31" # options used by the launcher by default, can be overridden by explicit # command line switches -default_options="--branding c1visualizer -J-XX:+UseStringDeduplication -J-DRepositoryUpdate.increasedLogLevel=800 -J-client -J-Xss2m -J-Xms32m -J-Dnetbeans.logger.console=true -J-Djdk.gtk.version=2.2 -J-Dapple.laf.useScreenMenuBar=true -J-Dapple.awt.graphics.UseQuartz=true -J-Dsun.java2d.noddraw=true -J-Dsun.java2d.dpiaware=true -J-Dsun.zip.disableMemoryMapping=true -J-Dplugin.manager.check.updates=false -J--add-opens=java.base/java.net=ALL-UNNAMED -J--add-opens=java.base/java.lang.ref=ALL-UNNAMED -J--add-opens=java.base/java.lang=ALL-UNNAMED -J--add-opens=java.base/java.security=ALL-UNNAMED -J--add-opens=java.base/java.util=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing.text=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing=ALL-UNNAMED -J--add-opens=java.desktop/java.awt=ALL-UNNAMED -J--add-opens=java.desktop/java.awt.event=ALL-UNNAMED -J--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED -J--add-opens=jdk.jshell/jdk.jshell=ALL-UNNAMED -J--add-modules=jdk.jshell -J--add-exports=java.desktop/sun.awt=ALL-UNNAMED -J--add-exports=java.desktop/java.awt.peer=ALL-UNNAMED -J--add-exports=java.desktop/com.sun.beans.editors=ALL-UNNAMED -J--add-exports=java.desktop/sun.swing=ALL-UNNAMED -J--add-exports=java.desktop/sun.awt.im=ALL-UNNAMED -J--add-exports=jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED -J--add-exports=java.management/sun.management=ALL-UNNAMED -J--add-exports=java.base/sun.reflect.annotation=ALL-UNNAMED -J--add-exports=jdk.javadoc/com.sun.tools.javadoc.main=ALL-UNNAMED -J--enable-native-access=ALL-UNNAMED -J-XX:+IgnoreUnrecognizedVMOptions" +default_options="--branding c1visualizer -J-XX:+UseStringDeduplication -J-DRepositoryUpdate.increasedLogLevel=800 -J-client -J-Xss2m -J-Xms32m -J-Dnetbeans.logger.console=true -J-Djdk.gtk.version=2.2 -J-Dapple.laf.useScreenMenuBar=true -J-Dapple.awt.graphics.UseQuartz=true -J-Dsun.java2d.noddraw=true -J-Dsun.java2d.dpiaware=true -J-Dsun.zip.disableMemoryMapping=true -J-Dplugin.manager.check.updates=false -J--add-opens=java.base/java.net=ALL-UNNAMED -J--add-opens=java.base/java.lang.ref=ALL-UNNAMED -J--add-opens=java.base/java.lang=ALL-UNNAMED -J--add-opens=java.base/java.security=ALL-UNNAMED -J--add-opens=java.base/java.util=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing.text=ALL-UNNAMED -J--add-opens=java.desktop/javax.swing=ALL-UNNAMED -J--add-opens=java.desktop/java.awt=ALL-UNNAMED -J--add-opens=java.desktop/java.awt.event=ALL-UNNAMED -J--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED -J--add-opens=jdk.jshell/jdk.jshell=ALL-UNNAMED -J--add-modules=jdk.jshell -J--add-exports=java.desktop/sun.awt=ALL-UNNAMED -J--add-exports=java.desktop/java.awt.peer=ALL-UNNAMED -J--add-exports=java.desktop/com.sun.beans.editors=ALL-UNNAMED -J--add-exports=java.desktop/sun.swing=ALL-UNNAMED -J--add-exports=java.desktop/sun.awt.im=ALL-UNNAMED -J--add-exports=jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED -J--add-exports=java.management/sun.management=ALL-UNNAMED -J--add-exports=java.base/sun.reflect.annotation=ALL-UNNAMED -J--enable-native-access=ALL-UNNAMED -J-XX:+IgnoreUnrecognizedVMOptions" # for development purposes you may wish to append: -J-Dnetbeans.logger.console=true -J-ea diff --git a/visualizer/C1Visualizer/application/src/main/resources/c1visualizer.icns b/visualizer/C1Visualizer/application/src/main/resources/c1visualizer.icns new file mode 100644 index 0000000000000000000000000000000000000000..7ab2b6abb3c0e03a1b499ae9540d3c9c886d4091 GIT binary patch literal 1041741 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�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≦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≦6Gg2LWFJ~r?( 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%0Dg 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

)6Uql&6|o---(FkQ&2h(yca(IX4TQS=My4`x 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

R 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 z�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 Kzq|jwpZpIaXmk<) literal 0 HcmV?d00001 diff --git a/visualizer/C1Visualizer/branding.jnlp b/visualizer/C1Visualizer/branding.jnlp deleted file mode 100644 index ee4c91c4888e..000000000000 --- a/visualizer/C1Visualizer/branding.jnlp +++ /dev/null @@ -1,15 +0,0 @@ - - - - - ${app.title} - ${app.title} vendor - ${app.name} application - - - ${jnlp.permissions} - - ${jnlp.branding.jars} - - - diff --git a/visualizer/C1Visualizer/master.jnlp b/visualizer/C1Visualizer/master.jnlp deleted file mode 100644 index f1dc77df9abe..000000000000 --- a/visualizer/C1Visualizer/master.jnlp +++ /dev/null @@ -1,25 +0,0 @@ - - - - - ${app.title} - ${app.title} vendor - ${app.name} application - - - - - - - - - - - - - - - --branding - ${branding.token} - - diff --git a/visualizer/mx.visualizer/mx_visualizer.py b/visualizer/mx.visualizer/mx_visualizer.py index 28d87114e638..b8d7d5ccc4bb 100644 --- a/visualizer/mx.visualizer/mx_visualizer.py +++ b/visualizer/mx.visualizer/mx_visualizer.py @@ -20,7 +20,6 @@ # questions. import mx -import mx_util import os import shutil from os.path import join, exists @@ -29,6 +28,7 @@ _suite = mx.suite('visualizer') igvDir = os.path.join(_suite.dir, 'IdealGraphVisualizer') +c1vDir = os.path.join(_suite.dir, 'C1Visualizer') class NetBeansProject(mx.ArchivableProject, mx.ClasspathDependency): def __init__(self, suite, name, deps, workingSets, theLicense, mxLibs=None, **args): @@ -44,11 +44,11 @@ def __init__(self, suite, name, deps, workingSets, theLicense, mxLibs=None, **ar if not hasattr(self, 'buildDependencies'): self.buildDependencies = [] self.buildDependencies += self.mxLibs - self.output_dirs = ['idealgraphvisualizer'] if self.dist else [] + self.output_dirs = [self.name.lower()] if self.dist else [] # by default skip the unit tests in a build self.build_commands = ["package", "-DskipTests"] - self.subDir = args.get('subDir') or None - self.baseDir = os.path.join(self.dir, self.subDir or 'IdealGraphVisualizer') + self.subDir = args['subDir'] + self.baseDir = os.path.join(self.dir, self.subDir) def is_test_project(self): return False @@ -57,13 +57,13 @@ def source_dirs(self): return [] def classpath_repr(self, resolve=True): - src = os.path.join(self.dir, 'IdealGraphVisualizer', self.name, 'src') + src = os.path.join(self.dir, self.name, self.name, 'src') if not os.path.exists(src): mx.abort(f"Cannot find {src}") return src def output_dir(self, relative=False): - outdir = os.path.join(_suite.dir, 'IdealGraphVisualizer', 'application', 'target') + outdir = os.path.join(_suite.dir, self.name, 'application', 'target') return outdir def source_gen_dir(self): @@ -84,7 +84,7 @@ def getBuildTask(self, args): return NetBeansBuildTask(self, args, None, None) def archive_prefix(self): - return 'igv' + return '' def annotation_processors(self): return [] @@ -116,33 +116,21 @@ def newestOutput(self): return None def build(self): - if self.subject.mxLibs: - libs_dir = os.path.join(self.subject.dir, self.subject.subDir or 'IdealGraphVisualizer', self.subject.name, 'lib') - mx_util.ensure_dir_exists(libs_dir) - for lib in self.subject.mxLibs: - lib_path = lib.classpath_repr(resolve=False) - link_name = os.path.join(libs_dir, os.path.basename(lib_path)) - from_path = os.path.relpath(lib_path, os.path.dirname(link_name)) - mx.log(f"Symlink {from_path}, {link_name}") - os.symlink(from_path, link_name) - mx.log('Symlinks must be copied!') - env = os.environ.copy() - env["MAVEN_OPTS"] = "-Djava.awt.headless=true -Dpolyglot.engine.WarnInterpreterOnly=false" + env["MAVEN_OPTS"] = "-Djava.awt.headless=true -Dpolyglot.engine.WarnInterpreterOnly=false --enable-native-access=ALL-UNNAMED" mx.logv(f"Setting PATH to {os.environ['PATH']}") - mx.log(f"Invoking maven for {' '.join(self.subject.build_commands)} for {self.subject.name} in {self.subject.baseDir}") run_maven(self.subject.build_commands, nonZeroIsFatal=True, cwd=self.subject.baseDir, env=env) for output_dir in self.subject.output_dirs: - os.chmod(os.path.join(self.subject.output_dir(), output_dir, 'bin', 'idealgraphvisualizer'), 0o755) + os.chmod(os.path.join(self.subject.output_dir(), output_dir, 'bin', self.subject.name.lower()), 0o755) mx.log(f'...finished build of {self.subject}') def clean(self, forBuild=False): if self.subject.mxLibs: - libs_dir = os.path.join(self.subject.dir, self.subject.subDir or 'IdealGraphVisualizer', self.subject.name, 'lib') + libs_dir = os.path.join(self.subject.dir, self.subject.subDir, self.subject.name, 'lib') for lib in self.subject.mxLibs: lib_path = lib.classpath_repr(resolve=False) link_name = os.path.join(libs_dir, os.path.basename(lib_path)) @@ -151,33 +139,48 @@ def clean(self, forBuild=False): if forBuild: return env = os.environ.copy() - env["MAVEN_OPTS"] = "-Djava.awt.headless=true" - run_maven(['clean', '--quiet'], resolve=False, nonZeroIsFatal=True, cwd=igvDir, env=env) + env["MAVEN_OPTS"] = "-Djava.awt.headless=true --enable-native-access=ALL-UNNAMED" + run_maven(['clean', '--quiet'], resolve=False, nonZeroIsFatal=True, cwd=os.path.join(_suite.dir, self.name), env=env) def run_maven(args, resolve=True, **kwargs): return mx.run(['mvn'] + args, **kwargs) -@mx.command(_suite.name, 'igv') -def igv(args): - """run the newly built Ideal Graph Visualizer""" +def build_and_run_netbeans(title, name, args, launchDir): # force a build if it hasn't been built yet - if not os.path.exists(os.path.join(_suite.dir, 'IdealGraphVisualizer', 'application', 'target', 'idealgraphvisualizer')): - mx.build(['--dependencies', 'IGV']) + if not os.path.exists(os.path.join(_suite.dir, title, 'application', 'target', name)): + mx.build(['--dependencies', title.upper()]) if mx.get_opts().verbose: args.append('-J-Dnetbeans.logger.console=true') - mx.run([join(igvDir, 'application', 'target', 'idealgraphvisualizer', 'bin', 'idealgraphvisualizer.exe' if mx.is_windows() else 'idealgraphvisualizer'), '--jdkhome', mx.get_jdk().home] + args) + mx.run([join(launchDir, 'application', 'target', name, 'bin', name + '.exe' if mx.is_windows() else name), '--jdkhome', mx.get_jdk().home] + args) + + +@mx.command(_suite.name, 'igv') +def igv(args): + """run the newly built Ideal Graph Visualizer""" + build_and_run_netbeans('IdealGraphVisualizer', 'idealgraphvisualizer', args, igvDir) + +@mx.command(_suite.name, 'c1visualizer') +def c1visualizer(args): + """run the newly built C1Visualizer""" + build_and_run_netbeans('C1Visualizer', 'c1visualizer', args, c1vDir) @mx.command(_suite.name, 'unittest') def test(args): """Run Ideal Graph Visualizer unit tests""" run_maven(['package'], nonZeroIsFatal=True, cwd=igvDir) + # C1Visualizer current has no unit tests -@mx.command(_suite.name, 'release') -def release(args): - """Build a released version using mvn release:prepare""" +@mx.command(_suite.name, 'releaseigv') +def releaseigv(args): + """Build a released version of IdealGraphVisualizer using mvn release:prepare""" run_maven(['-B', 'release:clean', 'release:prepare'], nonZeroIsFatal=True, cwd=igvDir) +@mx.command(_suite.name, 'releasec1v') +def releasec1v(args): + """Build a released version of C1Visualizer using mvn release:prepare""" + run_maven(['-B', 'release:clean', 'release:prepare'], nonZeroIsFatal=True, cwd=c1vDir) + @mx.command(_suite.name, 'verify-graal-graphio') def verify_graal_graphio(args): """Verify org.graalvm.graphio is unchanged between the compiler and visualizer folders""" diff --git a/visualizer/mx.visualizer/suite.py b/visualizer/mx.visualizer/suite.py index 187fc216468c..8c1585a816ff 100644 --- a/visualizer/mx.visualizer/suite.py +++ b/visualizer/mx.visualizer/suite.py @@ -42,14 +42,28 @@ "class": "NetBeansProject", "dist" : "true", }, + "C1Visualizer" : { + "subDir" : "C1Visualizer", + "sourceDirs" : ["src"], + "checkstyle" : "Data", + "class": "NetBeansProject", + "dist" : "true", + }, }, "distributions": { - "IGV": { + "IDEALGRAPHVISUALIZER": { "native" : True, "relpath" : True, "dependencies" : [ "IdealGraphVisualizer", ], }, + "C1VISUALIZER": { + "native" : True, + "relpath" : True, + "dependencies" : [ + "C1Visualizer", + ], + }, }, } From ac141d9752714bf2ff0ae063adc8abdf1f1f01c4 Mon Sep 17 00:00:00 2001 From: Tom Rodriguez Date: Wed, 12 Nov 2025 21:24:26 -0800 Subject: [PATCH 08/11] [maven-release-plugin] prepare release c1visualizer-1.13 --- visualizer/C1Visualizer/BlockView/pom.xml | 4 ++-- visualizer/C1Visualizer/BytecodeEditor/pom.xml | 4 ++-- visualizer/C1Visualizer/BytecodeModel/pom.xml | 4 ++-- visualizer/C1Visualizer/BytecodeView/pom.xml | 4 ++-- visualizer/C1Visualizer/CompilationModel/pom.xml | 4 ++-- visualizer/C1Visualizer/CompilationView/pom.xml | 4 ++-- visualizer/C1Visualizer/ControlFlowEditor/pom.xml | 4 ++-- visualizer/C1Visualizer/DataFlowEditor/pom.xml | 4 ++-- visualizer/C1Visualizer/DataFlowGraph/pom.xml | 4 ++-- visualizer/C1Visualizer/DataFlowView/pom.xml | 4 ++-- visualizer/C1Visualizer/GraphHelper/pom.xml | 4 ++-- visualizer/C1Visualizer/GraphLayoutAPI/pom.xml | 4 ++-- visualizer/C1Visualizer/GraphLayoutImpl/pom.xml | 4 ++-- visualizer/C1Visualizer/IntermediateCodeEditor/pom.xml | 4 ++-- visualizer/C1Visualizer/IntermediateCodeViews/pom.xml | 4 ++-- visualizer/C1Visualizer/IntervalEditor/pom.xml | 4 ++-- visualizer/C1Visualizer/IntervalView/pom.xml | 4 ++-- visualizer/C1Visualizer/NativeCodeEditor/pom.xml | 4 ++-- visualizer/C1Visualizer/NativeCodeView/pom.xml | 4 ++-- visualizer/C1Visualizer/TextEditor/pom.xml | 4 ++-- visualizer/C1Visualizer/VisualizerUI/pom.xml | 4 ++-- visualizer/C1Visualizer/application/pom.xml | 2 +- visualizer/C1Visualizer/branding/pom.xml | 4 ++-- visualizer/C1Visualizer/pom.xml | 4 ++-- 24 files changed, 47 insertions(+), 47 deletions(-) diff --git a/visualizer/C1Visualizer/BlockView/pom.xml b/visualizer/C1Visualizer/BlockView/pom.xml index 5a380fa39a9b..bbbff20cc6e4 100644 --- a/visualizer/C1Visualizer/BlockView/pom.xml +++ b/visualizer/C1Visualizer/BlockView/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer BlockView - 1.13-SNAPSHOT + 1.13 nbm BlockView diff --git a/visualizer/C1Visualizer/BytecodeEditor/pom.xml b/visualizer/C1Visualizer/BytecodeEditor/pom.xml index 737393dc56b0..ff8a41472459 100644 --- a/visualizer/C1Visualizer/BytecodeEditor/pom.xml +++ b/visualizer/C1Visualizer/BytecodeEditor/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer BytecodeEditor - 1.13-SNAPSHOT + 1.13 nbm BytecodeEditor diff --git a/visualizer/C1Visualizer/BytecodeModel/pom.xml b/visualizer/C1Visualizer/BytecodeModel/pom.xml index d1a72b3b3fa1..9d368faffab4 100644 --- a/visualizer/C1Visualizer/BytecodeModel/pom.xml +++ b/visualizer/C1Visualizer/BytecodeModel/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer BytecodeModel - 1.13-SNAPSHOT + 1.13 nbm BytecodeModel diff --git a/visualizer/C1Visualizer/BytecodeView/pom.xml b/visualizer/C1Visualizer/BytecodeView/pom.xml index 18b93e1bac30..be104f75a507 100644 --- a/visualizer/C1Visualizer/BytecodeView/pom.xml +++ b/visualizer/C1Visualizer/BytecodeView/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer BytecodeView - 1.13-SNAPSHOT + 1.13 nbm BytecodeView diff --git a/visualizer/C1Visualizer/CompilationModel/pom.xml b/visualizer/C1Visualizer/CompilationModel/pom.xml index 701fecef313d..5f7a3f7a3bd3 100644 --- a/visualizer/C1Visualizer/CompilationModel/pom.xml +++ b/visualizer/C1Visualizer/CompilationModel/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer CompilationModel - 1.13-SNAPSHOT + 1.13 nbm CompilationModel diff --git a/visualizer/C1Visualizer/CompilationView/pom.xml b/visualizer/C1Visualizer/CompilationView/pom.xml index 4a364f026324..10a52d93b6ef 100644 --- a/visualizer/C1Visualizer/CompilationView/pom.xml +++ b/visualizer/C1Visualizer/CompilationView/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer CompilationView - 1.13-SNAPSHOT + 1.13 nbm CompilationView diff --git a/visualizer/C1Visualizer/ControlFlowEditor/pom.xml b/visualizer/C1Visualizer/ControlFlowEditor/pom.xml index 731714f625fe..21d38bd2eaa1 100644 --- a/visualizer/C1Visualizer/ControlFlowEditor/pom.xml +++ b/visualizer/C1Visualizer/ControlFlowEditor/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer ControlFlowEditor - 1.13-SNAPSHOT + 1.13 nbm ControlFlowEditor diff --git a/visualizer/C1Visualizer/DataFlowEditor/pom.xml b/visualizer/C1Visualizer/DataFlowEditor/pom.xml index 38323aa321a6..760016b51063 100644 --- a/visualizer/C1Visualizer/DataFlowEditor/pom.xml +++ b/visualizer/C1Visualizer/DataFlowEditor/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer DataFlowEditor - 1.13-SNAPSHOT + 1.13 nbm DataFlowEditor diff --git a/visualizer/C1Visualizer/DataFlowGraph/pom.xml b/visualizer/C1Visualizer/DataFlowGraph/pom.xml index d6c3cbe6654c..cbfb4f8e7da3 100644 --- a/visualizer/C1Visualizer/DataFlowGraph/pom.xml +++ b/visualizer/C1Visualizer/DataFlowGraph/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer DataFlowGraph - 1.13-SNAPSHOT + 1.13 nbm DataFlowGraph diff --git a/visualizer/C1Visualizer/DataFlowView/pom.xml b/visualizer/C1Visualizer/DataFlowView/pom.xml index 5e245ebe7fa1..3e39998b8398 100644 --- a/visualizer/C1Visualizer/DataFlowView/pom.xml +++ b/visualizer/C1Visualizer/DataFlowView/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer DataFlowView - 1.13-SNAPSHOT + 1.13 nbm DataFlowView diff --git a/visualizer/C1Visualizer/GraphHelper/pom.xml b/visualizer/C1Visualizer/GraphHelper/pom.xml index a787d0045a87..23a781c25ab7 100644 --- a/visualizer/C1Visualizer/GraphHelper/pom.xml +++ b/visualizer/C1Visualizer/GraphHelper/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer GraphHelper - 1.13-SNAPSHOT + 1.13 nbm GraphHelper diff --git a/visualizer/C1Visualizer/GraphLayoutAPI/pom.xml b/visualizer/C1Visualizer/GraphLayoutAPI/pom.xml index 3bf2c013f7a3..c654692e18b6 100644 --- a/visualizer/C1Visualizer/GraphLayoutAPI/pom.xml +++ b/visualizer/C1Visualizer/GraphLayoutAPI/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer GraphLayoutAPI - 1.13-SNAPSHOT + 1.13 nbm GraphLayoutAPI diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/pom.xml b/visualizer/C1Visualizer/GraphLayoutImpl/pom.xml index dc90eccc75f1..b4d4416c5b3a 100644 --- a/visualizer/C1Visualizer/GraphLayoutImpl/pom.xml +++ b/visualizer/C1Visualizer/GraphLayoutImpl/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer GraphLayoutImpl - 1.13-SNAPSHOT + 1.13 nbm GraphLayoutImpl diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/pom.xml b/visualizer/C1Visualizer/IntermediateCodeEditor/pom.xml index 57dd0669a3b3..eca40232dcfa 100644 --- a/visualizer/C1Visualizer/IntermediateCodeEditor/pom.xml +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer IntermediateCodeEditor - 1.13-SNAPSHOT + 1.13 nbm IntermediateCodeEditor diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/pom.xml b/visualizer/C1Visualizer/IntermediateCodeViews/pom.xml index d6a24bb93958..370f33fd4b0b 100644 --- a/visualizer/C1Visualizer/IntermediateCodeViews/pom.xml +++ b/visualizer/C1Visualizer/IntermediateCodeViews/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer IntermediateCodeViews - 1.13-SNAPSHOT + 1.13 nbm IntermediateCodeViews diff --git a/visualizer/C1Visualizer/IntervalEditor/pom.xml b/visualizer/C1Visualizer/IntervalEditor/pom.xml index 1d62d1af26f2..7e488b7f9cf2 100644 --- a/visualizer/C1Visualizer/IntervalEditor/pom.xml +++ b/visualizer/C1Visualizer/IntervalEditor/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer IntervalEditor - 1.13-SNAPSHOT + 1.13 nbm IntervalEditor diff --git a/visualizer/C1Visualizer/IntervalView/pom.xml b/visualizer/C1Visualizer/IntervalView/pom.xml index 2cd220615ea2..53aec18c499b 100644 --- a/visualizer/C1Visualizer/IntervalView/pom.xml +++ b/visualizer/C1Visualizer/IntervalView/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer IntervalView - 1.13-SNAPSHOT + 1.13 nbm IntervalView diff --git a/visualizer/C1Visualizer/NativeCodeEditor/pom.xml b/visualizer/C1Visualizer/NativeCodeEditor/pom.xml index 2a8cc6d2acc2..eccc46230647 100644 --- a/visualizer/C1Visualizer/NativeCodeEditor/pom.xml +++ b/visualizer/C1Visualizer/NativeCodeEditor/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer NativeCodeEditor - 1.13-SNAPSHOT + 1.13 nbm NativeCodeEditor diff --git a/visualizer/C1Visualizer/NativeCodeView/pom.xml b/visualizer/C1Visualizer/NativeCodeView/pom.xml index 6707b348db74..c1f9541d7557 100644 --- a/visualizer/C1Visualizer/NativeCodeView/pom.xml +++ b/visualizer/C1Visualizer/NativeCodeView/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer NativeCodeView - 1.13-SNAPSHOT + 1.13 nbm NativeCodeView diff --git a/visualizer/C1Visualizer/TextEditor/pom.xml b/visualizer/C1Visualizer/TextEditor/pom.xml index c895716e78a5..d4df27736575 100644 --- a/visualizer/C1Visualizer/TextEditor/pom.xml +++ b/visualizer/C1Visualizer/TextEditor/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer TextEditor - 1.13-SNAPSHOT + 1.13 nbm TextEditor diff --git a/visualizer/C1Visualizer/VisualizerUI/pom.xml b/visualizer/C1Visualizer/VisualizerUI/pom.xml index f414ec728a46..7e7f9106f5ed 100644 --- a/visualizer/C1Visualizer/VisualizerUI/pom.xml +++ b/visualizer/C1Visualizer/VisualizerUI/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer VisualizerUI - 1.13-SNAPSHOT + 1.13 nbm VisualizerUI diff --git a/visualizer/C1Visualizer/application/pom.xml b/visualizer/C1Visualizer/application/pom.xml index e65810f06a83..62405dc25703 100644 --- a/visualizer/C1Visualizer/application/pom.xml +++ b/visualizer/C1Visualizer/application/pom.xml @@ -35,7 +35,7 @@ at.ssw.visualizer C1Visualizer-parent - 1.13-SNAPSHOT + 1.13 C1Visualizer-app nbm-application diff --git a/visualizer/C1Visualizer/branding/pom.xml b/visualizer/C1Visualizer/branding/pom.xml index b7065d2bef57..ae72a3c2be72 100644 --- a/visualizer/C1Visualizer/branding/pom.xml +++ b/visualizer/C1Visualizer/branding/pom.xml @@ -35,11 +35,11 @@ at.ssw.visualizer C1Visualizer-parent - 1.13-SNAPSHOT + 1.13 at.ssw.visualizer branding - 1.13-SNAPSHOT + 1.13 nbm branding diff --git a/visualizer/C1Visualizer/pom.xml b/visualizer/C1Visualizer/pom.xml index 32c0ca5e1900..ff2efde698f3 100644 --- a/visualizer/C1Visualizer/pom.xml +++ b/visualizer/C1Visualizer/pom.xml @@ -34,14 +34,14 @@ 4.0.0 at.ssw.visualizer C1Visualizer-parent - 1.13-SNAPSHOT + 1.13 pom C1Visualizer-parent scm:git:git@github.com:oracle/graal.git - HEAD + c1visualizer-1.13 From 1d8c657be099d75e67a4a565371f982063e41926 Mon Sep 17 00:00:00 2001 From: Tom Rodriguez Date: Wed, 12 Nov 2025 21:24:26 -0800 Subject: [PATCH 09/11] [maven-release-plugin] prepare for next development iteration --- visualizer/C1Visualizer/BlockView/pom.xml | 4 ++-- visualizer/C1Visualizer/BytecodeEditor/pom.xml | 4 ++-- visualizer/C1Visualizer/BytecodeModel/pom.xml | 4 ++-- visualizer/C1Visualizer/BytecodeView/pom.xml | 4 ++-- visualizer/C1Visualizer/CompilationModel/pom.xml | 4 ++-- visualizer/C1Visualizer/CompilationView/pom.xml | 4 ++-- visualizer/C1Visualizer/ControlFlowEditor/pom.xml | 4 ++-- visualizer/C1Visualizer/DataFlowEditor/pom.xml | 4 ++-- visualizer/C1Visualizer/DataFlowGraph/pom.xml | 4 ++-- visualizer/C1Visualizer/DataFlowView/pom.xml | 4 ++-- visualizer/C1Visualizer/GraphHelper/pom.xml | 4 ++-- visualizer/C1Visualizer/GraphLayoutAPI/pom.xml | 4 ++-- visualizer/C1Visualizer/GraphLayoutImpl/pom.xml | 4 ++-- visualizer/C1Visualizer/IntermediateCodeEditor/pom.xml | 4 ++-- visualizer/C1Visualizer/IntermediateCodeViews/pom.xml | 4 ++-- visualizer/C1Visualizer/IntervalEditor/pom.xml | 4 ++-- visualizer/C1Visualizer/IntervalView/pom.xml | 4 ++-- visualizer/C1Visualizer/NativeCodeEditor/pom.xml | 4 ++-- visualizer/C1Visualizer/NativeCodeView/pom.xml | 4 ++-- visualizer/C1Visualizer/TextEditor/pom.xml | 4 ++-- visualizer/C1Visualizer/VisualizerUI/pom.xml | 4 ++-- visualizer/C1Visualizer/application/pom.xml | 2 +- visualizer/C1Visualizer/branding/pom.xml | 4 ++-- visualizer/C1Visualizer/pom.xml | 4 ++-- 24 files changed, 47 insertions(+), 47 deletions(-) diff --git a/visualizer/C1Visualizer/BlockView/pom.xml b/visualizer/C1Visualizer/BlockView/pom.xml index bbbff20cc6e4..1649a167cbdf 100644 --- a/visualizer/C1Visualizer/BlockView/pom.xml +++ b/visualizer/C1Visualizer/BlockView/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer BlockView - 1.13 + 1.14-SNAPSHOT nbm BlockView diff --git a/visualizer/C1Visualizer/BytecodeEditor/pom.xml b/visualizer/C1Visualizer/BytecodeEditor/pom.xml index ff8a41472459..dcb904953196 100644 --- a/visualizer/C1Visualizer/BytecodeEditor/pom.xml +++ b/visualizer/C1Visualizer/BytecodeEditor/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer BytecodeEditor - 1.13 + 1.14-SNAPSHOT nbm BytecodeEditor diff --git a/visualizer/C1Visualizer/BytecodeModel/pom.xml b/visualizer/C1Visualizer/BytecodeModel/pom.xml index 9d368faffab4..22db187991f5 100644 --- a/visualizer/C1Visualizer/BytecodeModel/pom.xml +++ b/visualizer/C1Visualizer/BytecodeModel/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer BytecodeModel - 1.13 + 1.14-SNAPSHOT nbm BytecodeModel diff --git a/visualizer/C1Visualizer/BytecodeView/pom.xml b/visualizer/C1Visualizer/BytecodeView/pom.xml index be104f75a507..7e964ddccf19 100644 --- a/visualizer/C1Visualizer/BytecodeView/pom.xml +++ b/visualizer/C1Visualizer/BytecodeView/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer BytecodeView - 1.13 + 1.14-SNAPSHOT nbm BytecodeView diff --git a/visualizer/C1Visualizer/CompilationModel/pom.xml b/visualizer/C1Visualizer/CompilationModel/pom.xml index 5f7a3f7a3bd3..894e98598682 100644 --- a/visualizer/C1Visualizer/CompilationModel/pom.xml +++ b/visualizer/C1Visualizer/CompilationModel/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer CompilationModel - 1.13 + 1.14-SNAPSHOT nbm CompilationModel diff --git a/visualizer/C1Visualizer/CompilationView/pom.xml b/visualizer/C1Visualizer/CompilationView/pom.xml index 10a52d93b6ef..97286b56757b 100644 --- a/visualizer/C1Visualizer/CompilationView/pom.xml +++ b/visualizer/C1Visualizer/CompilationView/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer CompilationView - 1.13 + 1.14-SNAPSHOT nbm CompilationView diff --git a/visualizer/C1Visualizer/ControlFlowEditor/pom.xml b/visualizer/C1Visualizer/ControlFlowEditor/pom.xml index 21d38bd2eaa1..e91c6baae053 100644 --- a/visualizer/C1Visualizer/ControlFlowEditor/pom.xml +++ b/visualizer/C1Visualizer/ControlFlowEditor/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer ControlFlowEditor - 1.13 + 1.14-SNAPSHOT nbm ControlFlowEditor diff --git a/visualizer/C1Visualizer/DataFlowEditor/pom.xml b/visualizer/C1Visualizer/DataFlowEditor/pom.xml index 760016b51063..8c33f314547a 100644 --- a/visualizer/C1Visualizer/DataFlowEditor/pom.xml +++ b/visualizer/C1Visualizer/DataFlowEditor/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer DataFlowEditor - 1.13 + 1.14-SNAPSHOT nbm DataFlowEditor diff --git a/visualizer/C1Visualizer/DataFlowGraph/pom.xml b/visualizer/C1Visualizer/DataFlowGraph/pom.xml index cbfb4f8e7da3..13aebf85b058 100644 --- a/visualizer/C1Visualizer/DataFlowGraph/pom.xml +++ b/visualizer/C1Visualizer/DataFlowGraph/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer DataFlowGraph - 1.13 + 1.14-SNAPSHOT nbm DataFlowGraph diff --git a/visualizer/C1Visualizer/DataFlowView/pom.xml b/visualizer/C1Visualizer/DataFlowView/pom.xml index 3e39998b8398..ac238904597d 100644 --- a/visualizer/C1Visualizer/DataFlowView/pom.xml +++ b/visualizer/C1Visualizer/DataFlowView/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer DataFlowView - 1.13 + 1.14-SNAPSHOT nbm DataFlowView diff --git a/visualizer/C1Visualizer/GraphHelper/pom.xml b/visualizer/C1Visualizer/GraphHelper/pom.xml index 23a781c25ab7..665d5076716c 100644 --- a/visualizer/C1Visualizer/GraphHelper/pom.xml +++ b/visualizer/C1Visualizer/GraphHelper/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer GraphHelper - 1.13 + 1.14-SNAPSHOT nbm GraphHelper diff --git a/visualizer/C1Visualizer/GraphLayoutAPI/pom.xml b/visualizer/C1Visualizer/GraphLayoutAPI/pom.xml index c654692e18b6..b7a77dcdc2f2 100644 --- a/visualizer/C1Visualizer/GraphLayoutAPI/pom.xml +++ b/visualizer/C1Visualizer/GraphLayoutAPI/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer GraphLayoutAPI - 1.13 + 1.14-SNAPSHOT nbm GraphLayoutAPI diff --git a/visualizer/C1Visualizer/GraphLayoutImpl/pom.xml b/visualizer/C1Visualizer/GraphLayoutImpl/pom.xml index b4d4416c5b3a..8066b2b5e752 100644 --- a/visualizer/C1Visualizer/GraphLayoutImpl/pom.xml +++ b/visualizer/C1Visualizer/GraphLayoutImpl/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer GraphLayoutImpl - 1.13 + 1.14-SNAPSHOT nbm GraphLayoutImpl diff --git a/visualizer/C1Visualizer/IntermediateCodeEditor/pom.xml b/visualizer/C1Visualizer/IntermediateCodeEditor/pom.xml index eca40232dcfa..ce658f780088 100644 --- a/visualizer/C1Visualizer/IntermediateCodeEditor/pom.xml +++ b/visualizer/C1Visualizer/IntermediateCodeEditor/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer IntermediateCodeEditor - 1.13 + 1.14-SNAPSHOT nbm IntermediateCodeEditor diff --git a/visualizer/C1Visualizer/IntermediateCodeViews/pom.xml b/visualizer/C1Visualizer/IntermediateCodeViews/pom.xml index 370f33fd4b0b..f3a5dcac13e5 100644 --- a/visualizer/C1Visualizer/IntermediateCodeViews/pom.xml +++ b/visualizer/C1Visualizer/IntermediateCodeViews/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer IntermediateCodeViews - 1.13 + 1.14-SNAPSHOT nbm IntermediateCodeViews diff --git a/visualizer/C1Visualizer/IntervalEditor/pom.xml b/visualizer/C1Visualizer/IntervalEditor/pom.xml index 7e488b7f9cf2..8b86df76e073 100644 --- a/visualizer/C1Visualizer/IntervalEditor/pom.xml +++ b/visualizer/C1Visualizer/IntervalEditor/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer IntervalEditor - 1.13 + 1.14-SNAPSHOT nbm IntervalEditor diff --git a/visualizer/C1Visualizer/IntervalView/pom.xml b/visualizer/C1Visualizer/IntervalView/pom.xml index 53aec18c499b..dd1600e2aaa6 100644 --- a/visualizer/C1Visualizer/IntervalView/pom.xml +++ b/visualizer/C1Visualizer/IntervalView/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer IntervalView - 1.13 + 1.14-SNAPSHOT nbm IntervalView diff --git a/visualizer/C1Visualizer/NativeCodeEditor/pom.xml b/visualizer/C1Visualizer/NativeCodeEditor/pom.xml index eccc46230647..771623279e8a 100644 --- a/visualizer/C1Visualizer/NativeCodeEditor/pom.xml +++ b/visualizer/C1Visualizer/NativeCodeEditor/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer NativeCodeEditor - 1.13 + 1.14-SNAPSHOT nbm NativeCodeEditor diff --git a/visualizer/C1Visualizer/NativeCodeView/pom.xml b/visualizer/C1Visualizer/NativeCodeView/pom.xml index c1f9541d7557..d55a0ccdc6b6 100644 --- a/visualizer/C1Visualizer/NativeCodeView/pom.xml +++ b/visualizer/C1Visualizer/NativeCodeView/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer NativeCodeView - 1.13 + 1.14-SNAPSHOT nbm NativeCodeView diff --git a/visualizer/C1Visualizer/TextEditor/pom.xml b/visualizer/C1Visualizer/TextEditor/pom.xml index d4df27736575..0136c4dc7cc9 100644 --- a/visualizer/C1Visualizer/TextEditor/pom.xml +++ b/visualizer/C1Visualizer/TextEditor/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer TextEditor - 1.13 + 1.14-SNAPSHOT nbm TextEditor diff --git a/visualizer/C1Visualizer/VisualizerUI/pom.xml b/visualizer/C1Visualizer/VisualizerUI/pom.xml index 7e7f9106f5ed..0cba6d67c4c0 100644 --- a/visualizer/C1Visualizer/VisualizerUI/pom.xml +++ b/visualizer/C1Visualizer/VisualizerUI/pom.xml @@ -3,11 +3,11 @@ C1Visualizer-parent at.ssw.visualizer - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer VisualizerUI - 1.13 + 1.14-SNAPSHOT nbm VisualizerUI diff --git a/visualizer/C1Visualizer/application/pom.xml b/visualizer/C1Visualizer/application/pom.xml index 62405dc25703..039fe3284212 100644 --- a/visualizer/C1Visualizer/application/pom.xml +++ b/visualizer/C1Visualizer/application/pom.xml @@ -35,7 +35,7 @@ at.ssw.visualizer C1Visualizer-parent - 1.13 + 1.14-SNAPSHOT C1Visualizer-app nbm-application diff --git a/visualizer/C1Visualizer/branding/pom.xml b/visualizer/C1Visualizer/branding/pom.xml index ae72a3c2be72..c77ae61f4cc3 100644 --- a/visualizer/C1Visualizer/branding/pom.xml +++ b/visualizer/C1Visualizer/branding/pom.xml @@ -35,11 +35,11 @@ at.ssw.visualizer C1Visualizer-parent - 1.13 + 1.14-SNAPSHOT at.ssw.visualizer branding - 1.13 + 1.14-SNAPSHOT nbm branding diff --git a/visualizer/C1Visualizer/pom.xml b/visualizer/C1Visualizer/pom.xml index ff2efde698f3..c901bc74ede0 100644 --- a/visualizer/C1Visualizer/pom.xml +++ b/visualizer/C1Visualizer/pom.xml @@ -34,14 +34,14 @@ 4.0.0 at.ssw.visualizer C1Visualizer-parent - 1.13 + 1.14-SNAPSHOT pom C1Visualizer-parent scm:git:git@github.com:oracle/graal.git - c1visualizer-1.13 + HEAD From ef8fcc006c846fc24f8a8abc2bf9b6953d59fc8d Mon Sep 17 00:00:00 2001 From: Tom Rodriguez Date: Wed, 12 Nov 2025 21:25:58 -0800 Subject: [PATCH 10/11] update C1VISUALIZER_DIST --- compiler/mx.compiler/suite.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/mx.compiler/suite.py b/compiler/mx.compiler/suite.py index 8a2fd4b32570..d7107c1f6d44 100644 --- a/compiler/mx.compiler/suite.py +++ b/compiler/mx.compiler/suite.py @@ -58,8 +58,8 @@ }, "C1VISUALIZER_DIST" : { - "urls" : ["https://lafo.ssw.uni-linz.ac.at/pub/graal-external-deps/c1visualizer/c1visualizer-1.10.zip"], - "digest" : "sha512:40c505dd03ca0bb102f1091b89b90672126922f290bd8370eef9a7afc5d9c1e7b5db08c448a0948ef46bf57d850e166813e2d68bf7b1c88a46256d839b6b0201", + "urls" : ["https://lafo.ssw.uni-linz.ac.at/pub/c1visualizer/c1visualizer-1.13-3413409cce0.zip"], + "digest" : "sha512:176dcef9447f1760f70ec4da50b2f742e786fc3db6af9db9d699c303ecfe0e470deb3bb32120123cb93a0073f4f31cecffde2a7860edcf514dce9894d6df25c4", "packedResource": True, }, From 506672aa3d46cfc6355c13c171c2e85550ba66ab Mon Sep 17 00:00:00 2001 From: Tom Rodriguez Date: Tue, 25 Nov 2025 09:48:07 -0800 Subject: [PATCH 11/11] review fixes --- compiler/mx.compiler/mx_graal_tools.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/compiler/mx.compiler/mx_graal_tools.py b/compiler/mx.compiler/mx_graal_tools.py index 192b72a9e238..5c6da176f592 100644 --- a/compiler/mx.compiler/mx_graal_tools.py +++ b/compiler/mx.compiler/mx_graal_tools.py @@ -26,11 +26,11 @@ from __future__ import print_function import os -import shutil import re +import shutil import sys -from os.path import join, exists from argparse import ArgumentParser, REMAINDER +from os.path import join, exists import mx @@ -41,7 +41,7 @@ _suite = mx.suite('compiler') -def run_netbeans_app(app_name, jdkhome, args=None, dist=None): +def run_netbeans_app(app_name, jdkhome, args=None, dist=None, launch_message=None): args = [] if args is None else args if dist is None: dist = app_name.upper() + '_DIST' @@ -75,12 +75,12 @@ def run_netbeans_app(app_name, jdkhome, args=None, dist=None): if mx.get_os() == 'linux': # Mitigates X server crashes on Linux launch.append('-J-Dsun.java2d.xrender=false') - print('Consider flag -J-Dsun.java2d.uiScale=2 if on a high resolution display') - print('Consider flag -J-Xms4g -J-Xmx8g if dealing with large graphs') + if launch_message: + launch_message() mx.run(launch+args) -def netbeans_docstring(fullname, mixedname, mxname): +def netbeans_docstring(fullname, mxname): return f"""run the {fullname} The current version is based on NetBeans 26 which officially supports JDK 17 through JDK 24. A @@ -100,7 +100,7 @@ def netbeans_docstring(fullname, mixedname, mxname): """ -def launch_netbeans_app(fullname, mixedname, mxname, args): +def launch_netbeans_app(fullname, distname, mxname, args, launch_message=None): min_version = 17 max_version = 24 min_version_spec = mx.VersionSpec(str(min_version)) @@ -127,17 +127,21 @@ def _do_not_abort(msg): mx.warn(f'If you experience any problems try to use an LTS release between JDK {min_version} and JDK {max_version} instead.') mx.warn(f'mx help {mxname} provides more details.') - run_netbeans_app(mixedname, jdkhome, args=args, dist=f'{mixedname.upper()}_DIST') + run_netbeans_app(distname, jdkhome, args=args, dist=f'{distname.upper()}_DIST', launch_message=launch_message) def igv(args): - launch_netbeans_app('Ideal Graph Visualizer', 'IdealGraphVisualizer', 'igv', args) + def help_message(): + print('Consider flag -J-Dsun.java2d.uiScale=2 if on a high resolution display') + print('Consider flag -J-Xms4g -J-Xmx8g if dealing with large graphs') + + launch_netbeans_app('Ideal Graph Visualizer', 'IdealGraphVisualizer', 'igv', args, launch_message=help_message) -igv.__doc__ = netbeans_docstring('Ideal Graph Visualizer', 'IdealGraphVisualizer', 'igv') +igv.__doc__ = netbeans_docstring('Ideal Graph Visualizer', 'igv') def c1visualizer(args): launch_netbeans_app('C1 Visualizer', 'C1Visualizer', 'c1visualizer', args) -c1visualizer.__doc__ = netbeans_docstring('C1 Visualizer', 'C1Visualizer', 'c1visualizer') +c1visualizer.__doc__ = netbeans_docstring('C1 Visualizer', 'c1visualizer') def hsdis(args, copyToDir=None):