/* * 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. */ package org.apache.geode.sequence; import javax.swing.*; import java.awt.*; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.*; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by IntelliJ IDEA. User: dsmith Date: Oct 29, 2010 Time: 4:18:40 PM To change this * template use File | Settings | File Templates. */ public class SequenceDiagram extends JPanel { /** * */ private static final Color HIGHLIGHT_COLOR = Color.RED; private final List<String> lineNames; private final Map<String, List<String>> shortLineNames; private final SortedMap<Comparable, SubDiagram> subDiagrams = new TreeMap<Comparable, SubDiagram>(); private final StateColorMap colorMap = new StateColorMap(); private final long minTime; private final long maxTime; private static int PADDING_BETWEEN_LINES = 100; private static int Y_PADDING = 20; private static final int STATE_WIDTH = 20; private static final int LINE_LABEL_BOUNDARY = 5; private static final int AXIS_SIZE = 35; private int lineStep; private int lineWidth; private Popup mouseover; private LifelineState mouseoverState; private LifelineState selectedState; public SequenceDiagram(long minTime, long maxTime, List<String> lineNames, LineMapper lineMapper) { this.lineNames = lineNames; this.shortLineNames = parseShortNames(lineNames, lineMapper); this.minTime = minTime; this.maxTime = maxTime; int width = getInitialWidth(); int height = 500; super.setPreferredSize(new Dimension(width, height)); resizeMe(width, height); addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { Component source = (Component) e.getSource(); resizeMe(source.getWidth(), source.getHeight()); } }); setBackground(Color.WHITE); } private Map<String, List<String>> parseShortNames(List<String> lineNames, LineMapper lineMapper) { Map<String, List<String>> shortNames = new LinkedHashMap<String, List<String>>(lineNames.size()); for (String name : lineNames) { String shortName = lineMapper.getShortNameForLine(name); List<String> list = shortNames.get(shortName); if (list == null) { list = new ArrayList<String>(); shortNames.put(shortName, list); } list.add(name); } return shortNames; } public List<Comparable> getSubDiagramsNames() { return new ArrayList<Comparable>(subDiagrams.keySet()); } public void addSubDiagram(Comparable name, Map<String, Lifeline> lines, List<Arrow> arrows) { subDiagrams.put(name, new SubDiagram(name, lines, arrows)); } public void removeSubDiagram(Comparable name) { this.subDiagrams.remove(name); } private int getInitialWidth() { return (this.shortLineNames.size()) * PADDING_BETWEEN_LINES; } public void resizeMe(int width, int height) { this.setPreferredSize(new Dimension(width, height)); float xZoom = width / (float) getInitialWidth(); long elapsedTime = maxTime - minTime; double yScale = height / (double) elapsedTime; // long yBase = (long) (minTime - ((long) Y_PADDING / yScale)); long yBase = minTime; lineStep = (int) (PADDING_BETWEEN_LINES * xZoom); lineWidth = (int) (STATE_WIDTH * xZoom); if (subDiagrams.size() <= 0) { return; } int sublineWidth = lineWidth / subDiagrams.size(); int sublineIndex = 0; for (SubDiagram diagram : subDiagrams.values()) { int lineIndex = 0; for (List<String> fullNames : shortLineNames.values()) { for (String name : fullNames) { Lifeline line = diagram.lifelines.get(name); if (line != null) { int lineX = lineIndex * lineStep + sublineIndex * sublineWidth; line.resize(lineX, sublineWidth, yBase, yScale); } } lineIndex++; } sublineIndex++; } } public void showPopupText(int x, int y, int xOnScreen, int yOnScreen) { LifelineState state = getStateAt(x, y); if (state == mouseoverState) { return; } if (mouseover != null) { mouseover.hide(); } if (state == null) { mouseover = null; mouseoverState = null; } else { Component popupContents = state.getPopup(); mouseoverState = state; mouseover = PopupFactory.getSharedInstance().getPopup(this, popupContents, xOnScreen + 20, yOnScreen); mouseover.show(); } } public void selectState(int x, int y) { LifelineState state = getStateAt(x, y); if (state == selectedState) { return; } if (selectedState != null) { fireRepaintOfDependencies(selectedState); } selectedState = state; if (state != null) { fireRepaintOfDependencies(state); } } private LifelineState getStateAt(int x, int y) { // TODO - this needs some // serious optimization to go straight to the right state // I think we need a tree map of of lines, keyed by x offset // and a keymap of states keyed by y offset. // That could make painting faster as well. List<String> reverseList = new ArrayList<String>(lineNames); Collections.reverse(reverseList); for (SubDiagram diagram : subDiagrams.values()) { for (String name : reverseList) { Lifeline line = diagram.lifelines.get(name); if (line != null) { if (line.getX() < x && line.getX() + line.getWidth() > x) { LifelineState state = line.getStateAt(y); if (state != null) { return state; } } } } } return null; } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g.create(); // TODO - we should clip these to the visible lines for (SubDiagram subDiagram : subDiagrams.values()) { subDiagram.paintStates(g2, colorMap); } for (SubDiagram subDiagram : subDiagrams.values()) { subDiagram.paintArrows(g2, colorMap); } paintHighlightedComponents(g2, selectedState, new HashSet<LifelineState>()); } private void fireRepaintOfDependencies(LifelineState state) { // TODO - it might be more efficient to repaint just the changed // areas, but right now this will do. repaint(); } private void paintHighlightedComponents(Graphics2D g2, LifelineState endingState, Set<LifelineState> visited) { if (!visited.add(endingState)) { // Prevent cycles return; } g2.setColor(HIGHLIGHT_COLOR); if (endingState != null) { endingState.highlight(g2); for (Arrow arrow : endingState.getInboundArrows()) { arrow.paint(g2); paintHighlightedComponents(g2, arrow.getStartingState(), visited); } } } public long getMinTime() { return minTime; } public long getMaxTime() { return maxTime; } public JComponent createMemberAxis() { return new MemberAxis(); } private class MemberAxis extends JComponent { public MemberAxis() { setPreferredSize(new Dimension(getWidth(), AXIS_SIZE)); SequenceDiagram.this.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { int newWidth = e.getComponent().getWidth(); setPreferredSize(new Dimension(newWidth, AXIS_SIZE)); revalidate(); } }); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Rectangle bounds = g.getClipBounds(); int index = 0; for (String name : shortLineNames.keySet()) { int nameWidth = g.getFontMetrics().stringWidth(name); int lineX = lineStep * index; index++; if (bounds.getMaxX() < lineX || bounds.getMinX() > lineX + nameWidth) { continue; } g.setClip(lineX + LINE_LABEL_BOUNDARY, 0, lineStep - +LINE_LABEL_BOUNDARY * 2, getHeight()); g.drawString(name, lineX + LINE_LABEL_BOUNDARY, AXIS_SIZE / 3); g.setClip(null); } } } public static class SubDiagram { private final Map<String, Lifeline> lifelines; private final List<Arrow> arrows; private final Comparable name; public SubDiagram(Comparable name, Map<String, Lifeline> lines, List<Arrow> arrows) { this.name = name; this.lifelines = lines; this.arrows = arrows; } public void paintStates(Graphics2D g2, StateColorMap colorMap) { for (Lifeline line : lifelines.values()) { line.paint(g2, colorMap); } } public void paintArrows(Graphics2D g2, StateColorMap colorMap) { Color lineColor = colorMap.getColor(name); g2.setColor(lineColor); for (Arrow arrow : arrows) { arrow.paint(g2); } } } }