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 protected String format(IDisplayable value) { 113 if (value != null) { 114 // FIXME: Add formatter that does AUTO (EXACT) or so. 115 String auto = value.displayUsing(IDisplayable.AUTO); 116 // String exact = value.displayUsing(IDisplayable.EXACT); 117 // return (auto.equals(exact)) ? htmlify(auto) : htmlify(auto + " (" + exact + ')'); 118 return htmlify(auto); 119 } else { 120 return Messages.N_A; 121 } 122 } 123 124 protected void appendTagLI(Color color) { 125 if (color != null) { 126 imageMap.put("color." + colorNum, SWTColorToolkit.getColorThumbnail(SWTColorToolkit.asRGB(color))); //$NON-NLS-1$ 127 text.append("<li style='image' value='color.").append(colorNum).append("' "); //$NON-NLS-1$ //$NON-NLS-2$ 128 colorNum++; 129 } else { 130 text.append("<li "); //$NON-NLS-1$ 131 } 132 // text.append("bindent='").append(bulletIndent).append("'> "); //$NON-NLS-1$ //$NON-NLS-2$ 133 text.append("bindent='0'> "); //$NON-NLS-1$ 134 } 135 136 protected void appendTitle(String title) { 137 text.append("<p><b>").append(title).append("</b></p>"); //$NON-NLS-1$ //$NON-NLS-2$ 138 } 139 140 protected void appendAtIfNew(IDisplayable newAt) { 141 String newAtAsString = format(newAt); 142 if (!newAtAsString.equals(lastAt)) { 143 text.append("<p><span nowrap='true'>At ").append(newAtAsString).append(":<br/></span></p>"); //$NON-NLS-1$ //$NON-NLS-2$ 144 lastAt = newAtAsString; 145 } 146 } 147 148 @Override 149 public void visit(IPoint point) { 150 appendAtIfNew(point.getX()); 151 appendTagLI(point.getColor()); 152 String name = point.getName(); 153 text.append("<span nowrap='true'>"); //$NON-NLS-1$ 154 if (name != null) { 155 text.append(htmlify(name)).append(" = "); //$NON-NLS-1$ 156 } 157 text.append(format(point.getY())); 158 text.append("</span></li>"); //$NON-NLS-1$ 159 } 160 161 @Override 162 public void visit(IBucket bucket) { 163 appendAtIfNew(bucket.getRange()); 164 appendTagLI(bucket.getColor()); 165 text.append("<span nowrap='true'>"); //$NON-NLS-1$ 166 String name = bucket.getName(); 167 if (name != null) { 168 text.append(htmlify(name)); 169 text.append(" [").append(format(bucket.getWidth())).append("] = "); //$NON-NLS-1$ //$NON-NLS-2$ 170 } else { 171 text.append("[").append(format(bucket.getWidth())).append("]: "); //$NON-NLS-1$ //$NON-NLS-2$ 172 } 173 174 text.append(format(bucket.getY())); 175 text.append("</span></li>"); //$NON-NLS-1$ 176 } 177 178 // FIXME: One idea was to let the user see the details in Properties/StackTrace views by click-selecting an event. 179 @Override 180 public void visit(ISpan span) { 181 if (span.getDescription() != null) { 182 appendTitle(span.getDescription()); 183 } 184 appendAtIfNew(span.getRange()); 185 appendTagLI(span.getColor()); 186 // Would normally insert <span nowrap='true'> here, but since bold text is not displayed, 187 // it is inserted after the <b> element instead. 188 Object payload = span.getPayload(); 189 IItem item = AdapterUtil.getAdapter(payload, IItem.class); 190 if (payload instanceof IDisplayable) { 191 text.append("<span nowrap='true'>"); //$NON-NLS-1$ 192 text.append(format((IDisplayable) payload)).append(": "); //$NON-NLS-1$ 193 } else if (item != null) { 194 IType<IItem> type = ItemToolkit.getItemType(item); 195 text.append("<b>").append(htmlify(type.getName())).append("</b><span nowrap='true'>: "); //$NON-NLS-1$ //$NON-NLS-2$ 196 } 197 text.append(format(span.getWidth())); 198 text.append("</span></li>"); //$NON-NLS-1$ 199 if (item != null) { 200 IType<IItem> type = ItemToolkit.getItemType(item); 201 IMCStackTrace trace = null; 202 Iterator<IAttribute<?>> attributes = getAttributeStream(type).iterator(); 203 while (attributes.hasNext()) { 204 IAttribute<?> attribute = attributes.next(); 205 if (attribute.equals(JfrAttributes.EVENT_STACKTRACE)) { 206 trace = JfrAttributes.EVENT_STACKTRACE.getAccessor(type).getMember(item); 207 continue; 208 } 209 text.append("<p vspace='false'><span nowrap='true'>"); //$NON-NLS-1$ 210 text.append(htmlify(attribute.getName())).append(": "); //$NON-NLS-1$ 211 // FIXME: Format timestamp with higher precision 212 Object value = attribute.getAccessor(type).getMember(item); 213 String valueString = TypeHandling.getValueString(value); 214 text.append(htmlify(valueString)); 215 text.append("</span></p>"); //$NON-NLS-1$ 216 // Get value 217 } 218 if (trace != null) { 219 text.append("<p vspace='false'/>"); //$NON-NLS-1$ 220 text.append("<p vspace='false'>"); //$NON-NLS-1$ 221 text.append(htmlify(JfrAttributes.EVENT_STACKTRACE.getName())).append(":<br/>"); //$NON-NLS-1$ 222 appendStackTrace(trace, true, false, true, true, true, false); 223 text.append("</p>"); //$NON-NLS-1$ 224 225 } 226 } 227 } 228 229 private void appendStackTrace( 230 IMCStackTrace trace, boolean showReturnValue, boolean showReturnValuePackage, boolean showClassName, 231 boolean showClassPackageName, boolean showArguments, boolean showArgumentsPackage) { 232 String indent = " "; //$NON-NLS-1$ 233 String lineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$ 234 String stackTraceString = FormatToolkit.getHumanReadable(trace, showReturnValue, showReturnValuePackage, 235 showClassName, showClassPackageName, showArguments, showArgumentsPackage, 236 MAXIMUM_VISIBLE_STACK_TRACE_ELEMENTS, indent, null, lineSeparator); 237 stackTraceString = htmlify(stackTraceString); 238 stackTraceString = stackTraceString.replace(indent, INDENT_4_NBSP); 239 stackTraceString = stackTraceString.replace(lineSeparator, "<br/>"); //$NON-NLS-1$ 240 text.append(stackTraceString); 241 } 242 243 @Override 244 public void visit(ITick tick) { 245 text.append("<p><span nowrap='true'>"); //$NON-NLS-1$ 246 text.append(htmlify(tick.getValue().displayUsing(IDisplayable.VERBOSE))); 247 text.append("</span><br/></p>"); //$NON-NLS-1$ 248 } 249 250 private static String htmlify(String text) { 251 return XmlToolkit.escapeTagContent(text); 252 } 253 254 @Override 255 public void visit(ILane lane) { 256 text.append("<p><span nowrap='true'>"); //$NON-NLS-1$ 257 text.append(htmlify(NLS.bind(Messages.ChartToolTipProvider_CAPTION_NAME, lane.getLaneName()))); 258 text.append("</span><br/><span nowrap='true'>"); //$NON-NLS-1$ 259 if (lane.getLaneDescription() != null && !lane.getLaneDescription().isEmpty()) { 260 text.append( 261 htmlify(NLS.bind(Messages.ChartToolTipProvider_CAPTION_DESCRIPTION, lane.getLaneDescription()))); 262 } 263 text.append("</span></p>"); //$NON-NLS-1$ 264 } 265 }