1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * The contents of this file are subject to the terms of either the Universal Permissive License 7 * v 1.0 as shown at http://oss.oracle.com/licenses/upl 8 * 9 * or the following license: 10 * 11 * Redistribution and use in source and binary forms, with or without modification, are permitted 12 * provided that the following conditions are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions 15 * and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 18 * conditions and the following disclaimer in the documentation and/or other materials provided with 19 * the distribution. 20 * 21 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 22 * endorse or promote products derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 26 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 31 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 package org.openjdk.jmc.flightrecorder.ui.common; 34 35 import java.awt.Color; 36 import java.util.HashMap; 37 import java.util.Iterator; 38 import java.util.Map; 39 import java.util.stream.Stream; 40 41 import org.eclipse.osgi.util.NLS; 42 import org.eclipse.swt.graphics.Image; 43 import org.openjdk.jmc.common.IDisplayable; 44 import org.openjdk.jmc.common.IMCStackTrace; 45 import org.openjdk.jmc.common.item.IAttribute; 46 import org.openjdk.jmc.common.item.IItem; 47 import org.openjdk.jmc.common.item.IType; 48 import org.openjdk.jmc.common.item.ItemToolkit; 49 import org.openjdk.jmc.common.util.FormatToolkit; 50 import org.openjdk.jmc.common.util.TypeHandling; 51 import org.openjdk.jmc.common.util.XmlToolkit; 52 import org.openjdk.jmc.flightrecorder.JfrAttributes; 53 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; 54 import org.openjdk.jmc.ui.charts.IChartInfoVisitor; 55 import org.openjdk.jmc.ui.common.util.AdapterUtil; 56 import org.openjdk.jmc.ui.misc.SWTColorToolkit; 57 58 /** 59 * Default chart tooltip provider. Note that each instance is only used once. 60 */ 61 // FIXME: Rework to use StyledText instead, since FormText has overflow problems on HiDPI screens 62 // (at least up to and including Eclipse 4.5). Also, use indentation for scopes and "At" sections. 63 public class ChartToolTipProvider implements IChartInfoVisitor { 64 private static final int MAXIMUM_VISIBLE_STACK_TRACE_ELEMENTS = 10; 65 private static final String INDENT_4_NBSP = " "; //$NON-NLS-1$ 66 private static final String INITIAL_HTML = "<form>"; //$NON-NLS-1$ 67 68 protected StringBuilder text = new StringBuilder(INITIAL_HTML); 69 private final Map<String, Image> imageMap = new HashMap<>(); 70 private int colorNum; 71 protected int bulletIndent; 72 protected String lastAt; 73 74 /** 75 * Return the HTML. This method should typically only be called once. (Though technically, with 76 * the current implementation, as long as it returns null, it could be called again. There's a 77 * potential use case where this could be useful. If that is ever used, change this statement.) 78 * 79 * @return the HTML text or null if no tooltip should be shown. 80 */ 81 public String getHTML() { 82 if (text.length() <= INITIAL_HTML.length()) { 83 return null; 84 } 85 text.append("</form>"); //$NON-NLS-1$ 86 return text.toString(); 87 } 88 89 public Map<String, Image> getImages() { 90 return imageMap; 91 } 92 93 protected Stream<IAttribute<?>> getAttributeStream(IType<IItem> type) { 94 return type.getAttributes().stream(); 95 } 96 97 @Override 98 public boolean enterScope(String context, boolean fullyShown) { 99 if (!fullyShown) { 100 text.append("<p>").append(htmlify(context)).append("</p>"); //$NON-NLS-1$ //$NON-NLS-2$ 101 bulletIndent += 16; 102 return true; 103 } 104 return false; 105 } 106 107 @Override 108 public void leaveScope() { 109 bulletIndent -= 16; 110 } 111 112 @Override 113 public void hover(Object data) { 114 // Auto-generated method stub 115 } 116 117 protected String format(IDisplayable value) { 118 if (value != null) { 119 // FIXME: Add formatter that does AUTO (EXACT) or so. 120 String auto = value.displayUsing(IDisplayable.AUTO); 121 // String exact = value.displayUsing(IDisplayable.EXACT); 122 // return (auto.equals(exact)) ? htmlify(auto) : htmlify(auto + " (" + exact + ')'); 123 return htmlify(auto); 124 } else { 125 return Messages.N_A; 126 } 127 } 128 129 protected void appendTagLI(Color color) { 130 if (color != null) { 131 imageMap.put("color." + colorNum, SWTColorToolkit.getColorThumbnail(SWTColorToolkit.asRGB(color))); //$NON-NLS-1$ 132 text.append("<li style='image' value='color.").append(colorNum).append("' "); //$NON-NLS-1$ //$NON-NLS-2$ 133 colorNum++; 134 } else { 135 text.append("<li "); //$NON-NLS-1$ 136 } 137 // text.append("bindent='").append(bulletIndent).append("'> "); //$NON-NLS-1$ //$NON-NLS-2$ 138 text.append("bindent='0'> "); //$NON-NLS-1$ 139 } 140 141 protected void appendTitle(String title) { 142 text.append("<p><b>").append(title).append("</b></p>"); //$NON-NLS-1$ //$NON-NLS-2$ 143 } 144 145 protected void appendAtIfNew(IDisplayable newAt) { 146 String newAtAsString = format(newAt); 147 if (!newAtAsString.equals(lastAt)) { 148 text.append("<p><span nowrap='true'>At ").append(newAtAsString).append(":<br/></span></p>"); //$NON-NLS-1$ //$NON-NLS-2$ 149 lastAt = newAtAsString; 150 } 151 } 152 153 @Override 154 public void visit(IPoint point) { 155 appendAtIfNew(point.getX()); 156 appendTagLI(point.getColor()); 157 String name = point.getName(); 158 text.append("<span nowrap='true'>"); //$NON-NLS-1$ 159 if (name != null) { 160 text.append(htmlify(name)).append(" = "); //$NON-NLS-1$ 161 } 162 text.append(format(point.getY())); 163 text.append("</span></li>"); //$NON-NLS-1$ 164 } 165 166 @Override 167 public void visit(IBucket bucket) { 168 appendAtIfNew(bucket.getRange()); 169 appendTagLI(bucket.getColor()); 170 text.append("<span nowrap='true'>"); //$NON-NLS-1$ 171 String name = bucket.getName(); 172 if (name != null) { 173 text.append(htmlify(name)); 174 text.append(" [").append(format(bucket.getWidth())).append("] = "); //$NON-NLS-1$ //$NON-NLS-2$ 175 } else { 176 text.append("[").append(format(bucket.getWidth())).append("]: "); //$NON-NLS-1$ //$NON-NLS-2$ 177 } 178 179 text.append(format(bucket.getY())); 180 text.append("</span></li>"); //$NON-NLS-1$ 181 } 182 183 // FIXME: One idea was to let the user see the details in Properties/StackTrace views by click-selecting an event. 184 @Override 185 public void visit(ISpan span) { 186 if (span.getDescription() != null) { 187 appendTitle(span.getDescription()); 188 } 189 appendAtIfNew(span.getRange()); 190 appendTagLI(span.getColor()); 191 // Would normally insert <span nowrap='true'> here, but since bold text is not displayed, 192 // it is inserted after the <b> element instead. 193 Object payload = span.getPayload(); 194 IItem item = AdapterUtil.getAdapter(payload, IItem.class); 195 if (payload instanceof IDisplayable) { 196 text.append("<span nowrap='true'>"); //$NON-NLS-1$ 197 text.append(format((IDisplayable) payload)).append(": "); //$NON-NLS-1$ 198 } else if (item != null) { 199 IType<IItem> type = ItemToolkit.getItemType(item); 200 text.append("<b>").append(htmlify(type.getName())).append("</b><span nowrap='true'>: "); //$NON-NLS-1$ //$NON-NLS-2$ 201 } 202 text.append(format(span.getWidth())); 203 text.append("</span></li>"); //$NON-NLS-1$ 204 if (item != null) { 205 IType<IItem> type = ItemToolkit.getItemType(item); 206 IMCStackTrace trace = null; 207 Iterator<IAttribute<?>> attributes = getAttributeStream(type).iterator(); 208 while (attributes.hasNext()) { 209 IAttribute<?> attribute = attributes.next(); 210 if (attribute.equals(JfrAttributes.EVENT_STACKTRACE)) { 211 trace = JfrAttributes.EVENT_STACKTRACE.getAccessor(type).getMember(item); 212 continue; 213 } 214 text.append("<p vspace='false'><span nowrap='true'>"); //$NON-NLS-1$ 215 text.append(htmlify(attribute.getName())).append(": "); //$NON-NLS-1$ 216 // FIXME: Format timestamp with higher precision 217 Object value = attribute.getAccessor(type).getMember(item); 218 String valueString = TypeHandling.getValueString(value); 219 text.append(htmlify(valueString)); 220 text.append("</span></p>"); //$NON-NLS-1$ 221 // Get value 222 } 223 if (trace != null) { 224 text.append("<p vspace='false'/>"); //$NON-NLS-1$ 225 text.append("<p vspace='false'>"); //$NON-NLS-1$ 226 text.append(htmlify(JfrAttributes.EVENT_STACKTRACE.getName())).append(":<br/>"); //$NON-NLS-1$ 227 appendStackTrace(trace, true, false, true, true, true, false); 228 text.append("</p>"); //$NON-NLS-1$ 229 230 } 231 } 232 } 233 234 private void appendStackTrace( 235 IMCStackTrace trace, boolean showReturnValue, boolean showReturnValuePackage, boolean showClassName, 236 boolean showClassPackageName, boolean showArguments, boolean showArgumentsPackage) { 237 String indent = " "; //$NON-NLS-1$ 238 String lineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$ 239 String stackTraceString = FormatToolkit.getHumanReadable(trace, showReturnValue, showReturnValuePackage, 240 showClassName, showClassPackageName, showArguments, showArgumentsPackage, 241 MAXIMUM_VISIBLE_STACK_TRACE_ELEMENTS, indent, null, lineSeparator); 242 stackTraceString = htmlify(stackTraceString); 243 stackTraceString = stackTraceString.replace(indent, INDENT_4_NBSP); 244 stackTraceString = stackTraceString.replace(lineSeparator, "<br/>"); //$NON-NLS-1$ 245 text.append(stackTraceString); 246 } 247 248 @Override 249 public void visit(ITick tick) { 250 text.append("<p><span nowrap='true'>"); //$NON-NLS-1$ 251 text.append(htmlify(tick.getValue().displayUsing(IDisplayable.VERBOSE))); 252 text.append("</span><br/></p>"); //$NON-NLS-1$ 253 } 254 255 private static String htmlify(String text) { 256 return XmlToolkit.escapeTagContent(text); 257 } 258 259 @Override 260 public void visit(ILane lane) { 261 text.append("<p><span nowrap='true'>"); //$NON-NLS-1$ 262 text.append(htmlify(NLS.bind(Messages.ChartToolTipProvider_CAPTION_NAME, lane.getLaneName()))); 263 text.append("</span><br/><span nowrap='true'>"); //$NON-NLS-1$ 264 if (lane.getLaneDescription() != null && !lane.getLaneDescription().isEmpty()) { 265 text.append( 266 htmlify(NLS.bind(Messages.ChartToolTipProvider_CAPTION_DESCRIPTION, lane.getLaneDescription()))); 267 } 268 text.append("</span></p>"); //$NON-NLS-1$ 269 } 270 }