blob: 4edd4d0220d9f438d773a4e4cca5692199b94827 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2020 SAP AG, IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
* Benjamin Maskalla - patch for 318618, use createFromURL
* IBM Corporation - icon labels
*******************************************************************************/
package org.eclipse.mat.ui;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.logging.Logger;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.dynamichelpers.ExtensionTracker;
import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.mat.query.registry.QueryDescriptor;
import org.eclipse.mat.ui.internal.ErrorLogHandler;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
public class MemoryAnalyserPlugin extends AbstractUIPlugin
{
public static final String PLUGIN_ID = "org.eclipse.mat.ui"; //$NON-NLS-1$
public static final String EDITOR_ID = "org.eclipse.mat.ui.editors.HeapEditor"; //$NON-NLS-1$
private static final String PREFIX = "$nl$/icons/"; //$NON-NLS-1$
private static final String HEAPPREFIX = PREFIX + "heapobjects/"; //$NON-NLS-1$
public interface ISharedImages
{
String HEAP = HEAPPREFIX + "heapdump16.gif"; //$NON-NLS-1$
String HEAP_INFO = PREFIX + "heapdump_details.gif"; //$NON-NLS-1$
String OPEN_SNAPSHOT = PREFIX + "open_snapshot.gif";//$NON-NLS-1$
String CONSOLE = PREFIX + "console.gif"; //$NON-NLS-1$
String CONSOLE_PLUS = PREFIX + "console_plus.gif"; //$NON-NLS-1$
String CONSOLE_REMOVE = PREFIX + "remove_console.gif"; //$NON-NLS-1$
String COPY = PREFIX + "copy.gif"; //$NON-NLS-1$
String PLUS = PREFIX + "plus.gif"; //$NON-NLS-1$
String EXPERT_SYSTEM = PREFIX + "expert.gif"; //$NON-NLS-1$
String REFRESH = PREFIX + "refresh.gif"; //$NON-NLS-1$
String THREAD = PREFIX + "thread.gif"; //$NON-NLS-1$
String RETAINED_SET = PREFIX + "retainedSet.gif"; //$NON-NLS-1$
String PACKAGE = PREFIX + "package.gif"; //$NON-NLS-1$
String SYNCED = PREFIX + "synced.gif"; //$NON-NLS-1$
String SYNCED_DISABLED = PREFIX + "synced_disabled.gif"; //$NON-NLS-1$
String ID = PREFIX + "id.gif"; //$NON-NLS-1$
String SIZE = PREFIX + "size.gif"; //$NON-NLS-1$
String CLASS = HEAPPREFIX + "class.gif"; //$NON-NLS-1$
String CLASS_MIXED = HEAPPREFIX + "class_mixed.gif"; //$NON-NLS-1$
String CLASS_OLD = HEAPPREFIX + "class_old.gif"; //$NON-NLS-1$
String SUPERCLASS = HEAPPREFIX + "superclass.gif"; //$NON-NLS-1$
String NOTEPAD = PREFIX + "notepad.gif"; //$NON-NLS-1$
String ARGUMENTS_WIZARD = PREFIX + "fill_arguments_wiz.gif"; //$NON-NLS-1$
String QUERY = PREFIX + "query_browser.gif"; //$NON-NLS-1$
String QUERY_DISABLED = PREFIX + "query_disabled.gif"; //$NON-NLS-1$
String OQL = PREFIX + "oql.gif"; //$NON-NLS-1$
String IMPORT_REPORT = PREFIX + "import_report.gif"; //$NON-NLS-1$
String EXPORT_MENU = PREFIX + "export.gif"; //$NON-NLS-1$
String EXPORT_HTML = PREFIX + "export_html.gif"; //$NON-NLS-1$
String EXPORT_CSV = PREFIX + "export_csv.gif"; //$NON-NLS-1$
String EXPORT_TXT = PREFIX + "export_txt.gif"; //$NON-NLS-1$
String REFRESHING = PREFIX + "refreshing.gif"; //$NON-NLS-1$
String CALCULATOR = PREFIX + "calculator.gif";//$NON-NLS-1$
String FILTER = PREFIX + "filter.gif"; //$NON-NLS-1$
String GROUPING = PREFIX + "grouping.gif"; //$NON-NLS-1$
String COMPARE = PREFIX + "compare.gif"; //$NON-NLS-1$
String PERCENTAGE = PREFIX + "percentage.gif"; //$NON-NLS-1$
String INFO = PREFIX + "info.gif"; //$NON-NLS-1$
String HELP = PREFIX + "help.png"; //$NON-NLS-1$
String FIND = PREFIX + "find.gif"; //$NON-NLS-1$
String EXECUTE_QUERY = PREFIX + "execute_query.gif"; //$NON-NLS-1$
String SHOW_AS_HISTOGRAM = PREFIX + "as_histogram.gif"; //$NON-NLS-1$
String EXPLORE = PREFIX + "explore.gif"; //$NON-NLS-1$
String SHOW_PANE = PREFIX + "show_pane.gif"; //$NON-NLS-1$
String CLOSE_PANE = PREFIX + "close_pane.gif"; //$NON-NLS-1$
String CLOSE_BRANCH = PREFIX + "close_branch.gif"; //$NON-NLS-1$
String PINNED = PREFIX + "pinned.gif"; //$NON-NLS-1$
String MOVE_UP = PREFIX + "move_up.gif"; //$NON-NLS-1$
String MOVE_DOWN = PREFIX + "move_down.gif"; //$NON-NLS-1$
String REMOVE = PREFIX + "remove.gif"; //$NON-NLS-1$
String REMOVE_ALL = PREFIX + "removeall.gif"; //$NON-NLS-1$
String SELECT_COLUMN = PREFIX + "select_table.gif"; //$NON-NLS-1$
String SET_DIFFERENCE_A = PREFIX + "set_differenceA.gif"; //$NON-NLS-1$
String SET_DIFFERENCE_B = PREFIX + "set_differenceB.gif"; //$NON-NLS-1$
String SET_INTERSECTION = PREFIX + "set_intersection.gif"; //$NON-NLS-1$
String SET_SYMMETRIC_DIFFERENCE = PREFIX + "set_symmetric_difference.gif"; //$NON-NLS-1$
String SET_UNION = PREFIX + "set_union.gif"; //$NON-NLS-1$
}
private static MemoryAnalyserPlugin plugin;
private Map<ImageDescriptor, Image> imageCache = new HashMap<ImageDescriptor, Image>(20);
private Map<URI, ImageDescriptor> imagePathCache = new HashMap<URI, ImageDescriptor>(20);
private IExtensionTracker tracker;
private Logger logger;
private ErrorLogHandler errorLogHandler;
private boolean useParentHandlers;
// Mappings to permit textual descriptions of Images to be recovered from
// Images.
private Map<Image, String> imageTextMap = new HashMap<Image, String>(20);
private Map<ImageDescriptor, String> descriptorTextMap = new HashMap<ImageDescriptor, String>(20);
public MemoryAnalyserPlugin()
{}
@Override
public void start(BundleContext context) throws Exception
{
super.start(context);
plugin = this;
tracker = new ExtensionTracker(Platform.getExtensionRegistry());
// redirect logging from the analysis core into the Eclipse logging
// facility
logger = Logger.getLogger("org.eclipse.mat");//$NON-NLS-1$
useParentHandlers = logger.getUseParentHandlers();
logger.setUseParentHandlers(false);
errorLogHandler = new ErrorLogHandler();
logger.addHandler(errorLogHandler);
}
public void stop(BundleContext context) throws Exception
{
plugin = null;
tracker.close();
for (Image image : imageCache.values())
image.dispose();
imageCache.clear();
// Clear mappings from Image/Descriptor to descriptive text.
imageTextMap.clear();
descriptorTextMap.clear();
logger.removeHandler(errorLogHandler);
logger.setUseParentHandlers(useParentHandlers);
logger = null;
errorLogHandler = null;
super.stop(context);
}
public static MemoryAnalyserPlugin getDefault()
{
return plugin;
}
// //////////////////////////////////////////////////////////////
// image handling
// //////////////////////////////////////////////////////////////
public static ImageDescriptor getImageDescriptor(String path)
{
// Use singleton instance so that ImageDescriptor can be mapped to text.
return MemoryAnalyserPlugin.getDefault().getPluginImageDescriptor(path);
}
public static Image getImage(String name)
{
return MemoryAnalyserPlugin.getDefault().getImage(getImageDescriptor(name));
}
private ImageDescriptor getPluginImageDescriptor(String path)
{
ImageDescriptor descriptor = AbstractUIPlugin.imageDescriptorFromPlugin(PLUGIN_ID, path);
if (descriptor != null)
{ // Add map entry for new descriptor to appropriate text.
// This should not result in a memory leak, assuming that two
// equivalent ImageDescriptors match under equals().
// This is already assumed in the usage of imageCache.
descriptorTextMap.put(descriptor, getIconString(path));
}
return descriptor;
}
public Image getImage(ImageDescriptor descriptor)
{
Image image = imageCache.get(descriptor);
if (image == null && descriptor != null)
{
image = descriptor.createImage();
imageCache.put(descriptor, image);
// Map new Image to descriptive text.
// Should not cause memory leak as this must be a new descriptor.
imageTextMap.put(image, descriptorTextMap.get(descriptor));
}
return image;
}
public ImageDescriptor getImageDescriptor(URL path)
{
// Use URI for maps to avoid blocking equals operation
URI pathKey;
try
{
pathKey = path.toURI();
}
catch (URISyntaxException e)
{
// Will cause a missing image to be used instead
pathKey = null;
}
ImageDescriptor descriptor = imagePathCache.get(pathKey);
if (descriptor == null)
{
descriptor = ImageDescriptor.createFromURL(path);
imagePathCache.put(pathKey, descriptor);
// Map new descriptor to descriptive text for the Image.
// Should not cause a memory leak as this is a new descriptor,
// and equivalent descriptors should overwrite existing entries.
descriptorTextMap.put(descriptor, getIconString(path));
}
return descriptor;
}
public Image getImage(URL path)
{
return getImage(getImageDescriptor(path));
}
public ImageDescriptor getImageDescriptor(QueryDescriptor query)
{
URL url = query != null ? query.getIcon() : null;
return url != null ? getImageDescriptor(url) : null;
}
public Image getImage(QueryDescriptor query)
{
ImageDescriptor imageDescriptor = getImageDescriptor(query);
return imageDescriptor == null ? null : getImage(imageDescriptor);
}
/**
* @param url
* URL of image file for which a description is required.
* @return String with meaningful description of image given by input url.
*/
private String getIconString(URL url)
{
// Delegate lookup based on path element of URL.
return getIconString(url.getPath());
}
/**
* @param path
* String representing the path to an image file for which a
* description is needed.
* @return String with meaningful description of image located at input
* path. NLS enabled as the string is obtained from a properties
* file.
*/
private String getIconString(String path)
{
// Construct system independent string representing path below "icons"
// This is then used to map to a NLS enabled textual description of the
// image.
File imageFile = new File(path); // Full path
String[] iconPath = parseIconPath(imageFile); // Split into elements
String iconKey = buildIconKey(iconPath); // Construct key for property
String iconLabel = IconLabels.getString(iconKey); // Obtain NLS value
return iconLabel;
}
/**
* @param imageFile
* File representing the path to the image. This is converted
* into a String[] by splitting the path into elements below the
* /icons directory and stripping off the suffix. Returns null if
* the file is not below an /icons directory.
* @return String[] representing the path split into elements as above.
*/
private static String[] parseIconPath(File imageFile)
{
String[] iconPath = null; // Initial and default value to return.
ArrayList<String> pathList = new ArrayList<String>(); // Accumulator
// Strip off file suffix.
pathList.add(imageFile.getName().split("\\.")[0]); //$NON-NLS-1$
// Iterate backwards up the path, inserting the directory names at the
// front of the ArrayList.
// This results in a sequence matching the original order of the path.
// Do not include the common parent directory "/icons" or ancestors.
while (imageFile != null)
{ // iterate up the path
imageFile = imageFile.getParentFile();
if (imageFile != null) // There was a parent to include.
{
String fileName = imageFile.getName();
if (fileName.equals("icons")) // Iteration complete. //$NON-NLS-1$
{ // Convert ArrayList to array for return.
iconPath = pathList.toArray(new String[0]);
imageFile = null; // terminate loop
}
else
{ // More to do - prepend the name of parent to sequence.
pathList.add(0, fileName); // add parent to front of list
}
}
}
return iconPath; // Return parsed path, or null if unexpected error.
}
/**
* @param iconPath
* String[] representing path to icon file below /icons
* @return String A mangled version of the path with path separators
* replaced with '-' to use as a key into the properties file
* containing the textual descriptions of the icons. This utility is
* used offline to build the properties file, and at runtime to look
* up the icon labels from the NLS properties file(s).
*/
private static String buildIconKey(String[] iconPath)
{
if (iconPath == null)
return IconLabels.UNKNOWN_ICON_KEY;
// Initialize key with common prefix from IconLabels class.
StringBuffer propertyBuf = new StringBuffer(IconLabels.ICON_KEY_PREFIX);
// Iterate through iconPath appending each element after '-'
for (String pathStr : iconPath)
{
propertyBuf.append('-');
propertyBuf.append(pathStr);
}
return propertyBuf.toString(); // Return constructed key.
}
/**
* @param image
* The Image for which descriptive text is to be retrieved.
* @return Descriptive text for the Image object, retrieved from
* imageTextMap, or text indicating "unknown image" if not found.
*/
public String getImageText(Image image)
{
String text = imageTextMap.get(image); // May be null
// Return default string if image not in map.
return (text == null) ? IconLabels.getString(IconLabels.UNKNOWN_ICON_KEY) : text;
}
public IExtensionTracker getExtensionTracker()
{
return tracker;
}
// //////////////////////////////////////////////////////////////
// logging
// //////////////////////////////////////////////////////////////
public static void log(IStatus status)
{
getDefault().getLog().log(status);
}
public static void log(Throwable e)
{
log(e, Messages.MemoryAnalyserPlugin_InternalError);
}
public static void log(Throwable e, String message)
{
log(new Status(IStatus.ERROR, PLUGIN_ID, message, e));
}
// ///////////////////////////////////////////////////////////////////
// Main program and associated methods (all offline code)
// to generate icon label properties from icon file names.
// Not used in MAT runtime environment.
// Offline code to generate English labels for icons.
// The standard required for error handling robustness in this code is
// lower than would be expected for MAT runtime code, as it is run only
// as an offline generation utility (by MAT developers only, not by users).
// ///////////////////////////////////////////////////////////////////
/**
* @param args
* Input arguments are ignored. This Java program generates a
* properties file "iconlabels.properties" automatically, based
* on the content of the MAT icons directories. The locations of
* the /icons directories are hardcoded to be those under
* org.eclipse.mat.api and org.eclipse.mat.ui, relative to the
* current working directory which is assumed to be a project in
* the workspace. This is the case if this program is
* "Run as Java Application" within Eclipse, using the default
* working directory org.eclipse.mat.ui. The output is written to
* "iconlabels.properties" in the current working directory,
* which can then be copied to the required location for the
* properties file, org.eclipse.mat.ui/src/org/eclipse/mat/ui/.
* Error handling is coarse-grained: any Exception is caught and
* details are printed to System.out. Some other diagnostics are
* written to System.out if errors occur.
*/
@SuppressWarnings("nls")
public static void main(String[] args)
{
/*
* Note that the file output uses \r\n as the line separator, as this is
* required by the IBM NLS translation tools. This corresponds to 0x0D0A
* in ASCII/UTF-8 encoding. Hence explicit line separators of \r\n are
* used throughout the generator code.
*/
// File for output properties:
final String propsFilename = "icon_labels.properties";
// Header string required for translation tooling.
final String nlsHeaders = "# NLS_MESSAGEFORMAT_NONE\r\n" + "# NLS_ENCODING=UNICODE\r\n" + "# \r\n";
// Header comment for generated properties file, referring back to this
// program.
final String autoComment = "# This file is automatically generated by org.eclipse.mat.ui.MemoryAnalyserPlugin.main().\r\n"
+ "# Refer to the documentation/comments for this method for usage instructions.\r\n"
+ "# \r\n"
+ "# Any manual modifications to this file will need to be reapplied if the file is regenerated.\r\n"
+ "# Therefore it is preferable if such modifications are kept to a minimum, or preferably\r\n"
+ "# achieved by amending the label generation code in MemoryAnalyserPlugin.buildIconLabel().\r\n"
+ "# \r\n";
// String to include special property to denote the "unknown icon"
// value.
final String unknownIconProperty = "# Icon label property to be used for an unknown icon:\r\n"
+ IconLabels.UNKNOWN_ICON_KEY + "=" + buildIconLabel(null) + "\r\n";
// String to indicate start of auto-generated label properties.
final String autoIconsComment = "# Automatically generated icon label properties:";
final String[] iconDirs = { // UI draws icons from several locations
// Assume the following two top-level icons directories relative to
// current directory.
"../org.eclipse.mat.api/META-INF/icons", "../org.eclipse.mat.ui/icons", "../org.eclipse.mat.hprof/icons", "../org.eclipse.mat.jdt/icons" };
try
// Trap any Exceptions at the outermost level.
{
// Use a sorted map for the properties so that the ordering is
// reproducible.
Map<String, String> iconMap = new TreeMap<String, String>();
for (String iconDir : iconDirs) // For each /icons directory
// (currently 4).
{
File iconDirFile = new File(iconDir);
if (iconDirFile.isDirectory()) // Check input is valid directory
{ // Generate properties for the directory and add to map.
generateIconProps(iconDirFile, iconMap);
}
else
{ // Error case - report to user.
System.out.println("Input is not a directory: " + iconDir);
}
}
// Now write out iconlabels.properties
File iconLabelsFile = new File(propsFilename);
PrintStream iconLabelsStream = null;
// Properties files are always encoded in ISO-8859-1
iconLabelsStream = new PrintStream(new FileOutputStream(iconLabelsFile), false, "ISO-8859-1");
// Print NLS headers required for translation
// Use printPropertyLine() to insert \r\n separator.
printPropertyLine(iconLabelsStream, nlsHeaders);
// Print header comment referring to this generator code.
printPropertyLine(iconLabelsStream, autoComment);
// Print special label to be used for unknown icons.
printPropertyLine(iconLabelsStream, unknownIconProperty);
// Print special label to be used for unknown icons.
printPropertyLine(iconLabelsStream, autoIconsComment);
// Print out iconMap entries, in collation sequence for
// reproducibility.
for (Entry<String, String> mapEntry : iconMap.entrySet())
{
iconLabelsStream.print(mapEntry.getKey());
iconLabelsStream.print('=');
printPropertyLine(iconLabelsStream, mapEntry.getValue());
}
iconLabelsStream.close();
// Print completion message to console
System.out.println("Icon label properties written to file: " + iconLabelsFile.getAbsolutePath());
}
catch (Exception e) // Catch all exceptions and print details to
// System.out
{
System.out.println(e.toString());
e.printStackTrace(System.out);
}
}
/**
* @param stream
* PrintStream to print line to.
* @param line
* String to append to PrintStream. Writes line to stream (just
* like println()) but using "\r\n" as line terminator to satisfy
* requirements of IBM translation tools.
*/
private static void printPropertyLine(PrintStream stream, String line)
{
stream.print(line);
stream.print("\r\n"); // Use MS-DOS line termination (0D0A) for
}
/**
* @param iconDir
* File representing a directory to search for icon files and to
* add the generated key/value pairs to iconMap.
* @param iconMap
* Map to add the generated key/value pairs for the icons. This
* should be a sorted Map to ensure reproducible ordering of
* output. Recursively invoked method to write out generated
* labels for icons in the form of a properties file. The
* recursion excludes hidden files & directories
*/
private static void generateIconProps(File iconDir, Map<String, String> iconMap)
{ // precondition: iconDir is a directory.
File[] fileList = iconDir.listFiles(); // Should not be null, may be
// empty.
for (File file : fileList) // Iterate over directory contents.
{
if (!file.isHidden())
{ // Exclude hidden files eg .svn directories
if (file.isDirectory())
{ // Directory, so recurse into child directory.
generateIconProps(file, iconMap);
}
else
{ // File, so add key/value pair to Map.
String[] iconPath = parseIconPath(file);
String key = buildIconKey(iconPath);
String label = buildIconLabel(iconPath);
iconMap.put(key, label); // Duplicate key will overwrite
}
} // if()
}
}
/**
* @param iconPath
* path of icon below "/icons", without file qualifier(s), parsed
* into String[] by
* @return We simply return a line representing a textual description for
* the icon, based on it's file location & name. This automated
* process can be adjusted to produce any desired result, provided
* the resulting line is a valid Java property value. Performance is
* not important as this is non-runtime code. It's more important
* that the results should be readily tailorable.
*/
@SuppressWarnings("nls")
private static String buildIconLabel(String[] iconPath)
{
// Input is path of icon below "/icons", without file qualifier(s).
if (iconPath == null) // Invalid path, icon may not exist.
return "unknown icon"; // Special case label.
StringBuffer labelBuf = new StringBuffer(); // Initially empty
// Split base file name of icon into tokens delimited by '_'
String[] iconName = iconPath[iconPath.length - 1].split("_");
for (String nameElem : iconName)
{ // For each component token, perform required tailoring.
// Expand common abbreviations
if (nameElem.equals("obj"))
nameElem = "object";
if (nameElem.equals("frgmt"))
nameElem = "fragment";
if (nameElem.equals("frgmts"))
nameElem = "fragments";
if (nameElem.equals("attr"))
nameElem = "attribute";
if (nameElem.equals("ext"))
nameElem = "extension";
if (nameElem.equals("mpaths"))
nameElem = "merge paths";
// Add to buffer
labelBuf.append(nameElem);
labelBuf.append(' '); // Add space between elements
}
// Iterate backwards up path, adding parent directory names to text.
for (int ipath = iconPath.length - 2; ipath >= 1; ipath--)
{ // omit top-level directory if there is one (index 0 not included).
labelBuf.append(iconPath[ipath]);
labelBuf.append(' '); // Appends trailing blank which is OK for
// properties
}
return labelBuf.toString(); // Convert to String and return.
}
}