sysperfana/analyzetoolext/com.nokia.s60tools.analyzetool/src/com/nokia/s60tools/analyzetool/internal/ui/graph/MemOpDescriptor.java
author Matti Laitinen <matti.t.laitinen@nokia.com>
Tue, 24 Aug 2010 12:16:27 +0300
changeset 15 0367d2db2c06
parent 6 f65f740e69f9
permissions -rw-r--r--
AnalyzeTool Carbide extension 1.10.0

/*
 * Copyright (c) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 * This component and the accompanying materials are made available
 * under the terms of "Eclipse Public License v1.0"
 * which accompanies this distribution, and is available
 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
 *
 * Initial Contributors:
 * Nokia Corporation - initial contribution.
 *
 * Contributors:
 *
 * Description:  Definitions for the class MemOpDescriptor
 *
 */

package com.nokia.s60tools.analyzetool.internal.ui.graph;

import java.io.IOException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.TextPropertyDescriptor;

import com.nokia.carbide.cdt.builder.CarbideBuilderPlugin;
import com.nokia.carbide.cdt.builder.project.ICarbideProjectInfo;
import com.nokia.cdt.debug.cw.symbian.symbolreader.IFunction;
import com.nokia.cdt.debug.cw.symbian.symbolreader.ISourceLocation;
import com.nokia.cdt.debug.cw.symbian.symbolreader.ISymbolFile;
import com.nokia.s60tools.analyzetool.Activator;
import com.nokia.s60tools.analyzetool.engine.ICallstackManager;
import com.nokia.s60tools.analyzetool.engine.statistic.AllocCallstack;
import com.nokia.s60tools.analyzetool.engine.statistic.AllocInfo;
import com.nokia.s60tools.analyzetool.engine.statistic.BaseInfo;
import com.nokia.s60tools.analyzetool.engine.statistic.DllLoad;
import com.nokia.s60tools.analyzetool.engine.statistic.SourceFile;
import com.nokia.s60tools.analyzetool.engine.statistic.SymReader;
import com.nokia.s60tools.analyzetool.global.Constants;
import com.nokia.s60tools.analyzetool.global.Util;
import com.nokia.s60tools.analyzetool.internal.ui.util.GraphUtils;

/**
 * MemOpDescriptor provides properties of any Alloc/Leak/Free suitable to be
 * displayed in eclipse Properties View.
 * 
 */
@SuppressWarnings("restriction")
class MemOpDescriptor implements IPropertySource {
	BaseInfo memInfo;

	/** process Id descriptor */
	private static final String PID_ID = "pid"; //$NON-NLS-1$
	private final DotTextPropertyDescriptor PID_DESCRIPTOR = new DotTextPropertyDescriptor(
			PID_ID, Messages.MemOpDescriptor_1);

	/** Memory operation type Allocation/Leak/Free descriptor */
	private static final String TYPE_ID = "type"; //$NON-NLS-1$
	private final DotTextPropertyDescriptor TYPE_DESCRIPTOR = new DotTextPropertyDescriptor(
			TYPE_ID, Messages.MemOpDescriptor_3);

	/** Time descriptor */
	private static final String TIME_ID = "time"; //$NON-NLS-1$
	private final DotTextPropertyDescriptor TIME_DESCRIPTOR = new DotTextPropertyDescriptor(
			TIME_ID, Messages.MemOpDescriptor_5);

	/** size descriptor */
	private static final String SIZE_ID = "size"; //$NON-NLS-1$
	private final DotTextPropertyDescriptor SIZE_DESCRIPTOR = new DotTextPropertyDescriptor(
			SIZE_ID, Messages.MemOpDescriptor_7);

	/** memory address descriptor */
	private static final String ADDR_ID = "address"; //$NON-NLS-1$
	private final DotTextPropertyDescriptor ADDR_DESCRIPTOR = new DotTextPropertyDescriptor(
			ADDR_ID, Messages.MemOpDescriptor_9);

	/** total memory size consumed by the process descriptor */
	private static final String TSIZE_ID = "total"; //$NON-NLS-1$
	private final DotTextPropertyDescriptor TSIZE_DESCRIPTOR = new DotTextPropertyDescriptor(
			TSIZE_ID, Messages.MemOpDescriptor_11);

	/** thread id descriptor */
	private static final String THREAD_ID = "thread"; //$NON-NLS-1$
	private final DotTextPropertyDescriptor THREAD_DESCRIPTOR = new DotTextPropertyDescriptor(
			THREAD_ID, Messages.MemOpDescriptor_10);

	/**
	 * life time of an allocation descriptor. This applies only to non Leaked
	 * allocations
	 */
	private static final String LIFETIME_ID = "lifetime"; //$NON-NLS-1$
	private final DotTextPropertyDescriptor LIFETIME_DESCRIPTOR = new DotTextPropertyDescriptor(
			LIFETIME_ID, Messages.MemOpDescriptor_13);
	private static final String ATTRIBUTES_GROUP = "Attributes"; //$NON-NLS-1$
	/** used for making absolute time values relative */
	private long baseTime;

	/** callstack item descriptor id */
	private static final String CALL_STACK_ID = "callstack"; //$NON-NLS-1$

	/** current project */
	IProject iCurrentProject = null;
	/** Symbol Reader */
	SymReader iSymReader = null;
	/** c++ files from the current project */
	AbstractList<String> cppFileNames;
	static final String LINE_SEPARATOR = " :: "; //$NON-NLS-1$

	private ICallstackManager callstackManager;

	/**
	 * Constructor
	 * 
	 * @param newBaseTime
	 *            usually process start time, used for making absolute time
	 *            values relative
	 * @param info
	 *            the alloc or free info
	 * @param project
	 *            IProject to use for locating source file
	 * @param symReader
	 *            the SymReader to use for pinpointing
	 * @param cppFiles
	 * @param callstackManager
	 *            CallstackManager for reading callstacks from BaseInfo
	 */
	public MemOpDescriptor(Long newBaseTime, BaseInfo info, IProject project,
			SymReader symReader, AbstractList<String> cppFiles,
			ICallstackManager callstackManager) {
		memInfo = info;
		iCurrentProject = project;
		iSymReader = symReader;
		cppFileNames = cppFiles;
		this.baseTime = newBaseTime;
		this.callstackManager = callstackManager;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.views.properties.IPropertySource#getEditableValue()
	 */
	public Object getEditableValue() {
		return null; // no edit
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.ui.views.properties.IPropertySource#getPropertyDescriptors()
	 */
	public IPropertyDescriptor[] getPropertyDescriptors() {
		final List<IPropertyDescriptor> completeList = new ArrayList<IPropertyDescriptor>();
		PID_DESCRIPTOR.setCategory(ATTRIBUTES_GROUP);
		completeList.add(PID_DESCRIPTOR);
		TYPE_DESCRIPTOR.setCategory(ATTRIBUTES_GROUP);
		completeList.add(TYPE_DESCRIPTOR);
		TIME_DESCRIPTOR.setCategory(ATTRIBUTES_GROUP);
		completeList.add(TIME_DESCRIPTOR);
		SIZE_DESCRIPTOR.setCategory(ATTRIBUTES_GROUP);
		completeList.add(SIZE_DESCRIPTOR);
		ADDR_DESCRIPTOR.setCategory(ATTRIBUTES_GROUP);
		completeList.add(ADDR_DESCRIPTOR);
		TSIZE_DESCRIPTOR.setCategory(ATTRIBUTES_GROUP);
		completeList.add(TSIZE_DESCRIPTOR);
		THREAD_DESCRIPTOR.setCategory(ATTRIBUTES_GROUP);
		completeList.add(THREAD_DESCRIPTOR);
		if (memInfo instanceof AllocInfo && ((AllocInfo) memInfo).isFreed()) {
			LIFETIME_DESCRIPTOR.setCategory(ATTRIBUTES_GROUP);
			completeList.add(LIFETIME_DESCRIPTOR); // only non leaks
		}
		// add callstack descriptors
		if (callstackManager != null && callstackManager.hasCallstack(memInfo)) {
			try {
				List<AllocCallstack> callstack = callstackManager
						.readCallstack(memInfo);
				if (callstack != null) {
					for (int i = 0; i < callstack.size(); i++) {

						final DotTextPropertyDescriptor propDesc = new DotTextPropertyDescriptor(
								i, CALL_STACK_ID);
						propDesc.setCategory("CallStack"); //$NON-NLS-1$
						completeList.add(propDesc);
					}
				}
			} catch (IOException e) {
				// since callstacks aren't fatal and we can't handle it usefully
				// here, log this to the error log
				Activator.getDefault().log(IStatus.ERROR,
						Messages.MemOpDescriptor_18, e);
			}
		}
		return completeList.toArray(new TextPropertyDescriptor[completeList
				.size()]);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.ui.views.properties.IPropertySource#getPropertyValue(java
	 * .lang.Object)
	 */
	public Object getPropertyValue(Object id) {
		// Note, this method must return String values for the CellEditor to
		// work
		if (PID_ID.equals(id)) {
			return String.valueOf(memInfo.getProcessID());// process ids are
			// usually decimal
			// values
		}
		if (TYPE_ID.equals(id))
			return memInfo instanceof AllocInfo ? ((AllocInfo) memInfo)
					.isFreed() ? Messages.MemOpDescriptor_19
					: Messages.MemOpDescriptor_20 : Messages.MemOpDescriptor_21;
		if (TIME_ID.equals(id))
			return GraphUtils.renderTime(memInfo.getTime() - baseTime);
		if (SIZE_ID.equals(id))
			return String.format(Messages.MemOpDescriptor_22, memInfo
					.getSizeInt());
		if (ADDR_ID.equals(id))
			return Long.toString(memInfo.getMemoryAddress(), 16);
		if (TSIZE_ID.equals(id))
			return String.format(Messages.MemOpDescriptor_23, memInfo
					.getTotalMem());
		if (THREAD_ID.equals(id))
			return String.valueOf(memInfo.getThreadId());
		if (LIFETIME_ID.equals(id)) {
			if (memInfo instanceof AllocInfo && ((AllocInfo) memInfo).isFreed()) {
				AllocInfo info = (AllocInfo) memInfo;
				return GraphUtils.renderTime(info.getFreedBy().getTime()
						- info.getTime());
			}
			throw new IllegalStateException(
					"Should not happen because we did not provide a lifetime descriptor for leak and free."); //$NON-NLS-1$
		}
		if (id instanceof Integer && callstackManager.hasCallstack(memInfo)) {
			int callstackId = (Integer) id;
			try {
				List<AllocCallstack> callstackList = callstackManager
						.readCallstack(memInfo);
				if (callstackId < callstackList.size()) {
					AllocCallstack callstackItem = callstackList
							.get(callstackId);
					DllLoad tempLoad = callstackItem.getDllLoad();
					long addr = callstackItem.getMemoryAddress();

					String name = String.format(Messages.MemOpDescriptor_25,
							addr);
					if (tempLoad != null
							&& callstackItem.getMemoryAddress() != tempLoad
									.getStartAddress()) {
						SourceFile aSourcefile = pinpoint(callstackItem
								.getMemoryAddress(), callstackItem.getDllLoad());
						if (aSourcefile != null) { // callstack resolved to a
							// file-function-line
							return name + LINE_SEPARATOR
									+ callstackItem.getDllLoad().getName()
									+ LINE_SEPARATOR
									+ aSourcefile.getFileName()
									+ LINE_SEPARATOR
									+ aSourcefile.getFunctionName()
									+ LINE_SEPARATOR
									+ aSourcefile.getLineNumber();
						}
					}
					return name
							+ (tempLoad != null ? LINE_SEPARATOR
									+ tempLoad.getName() : ""); //$NON-NLS-1$
				}
			} catch (IOException e) {
				// since callstacks aren't fatal and we can't handle it usefully
				// here, log this to the error log
				Activator.getDefault().log(IStatus.ERROR,
						Messages.MemOpDescriptor_27, e);
			}
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.ui.views.properties.IPropertySource#isPropertySet(java.lang
	 * .Object)
	 */
	public boolean isPropertySet(Object id) {
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.ui.views.properties.IPropertySource#resetPropertyValue(java
	 * .lang.Object)
	 */
	public void resetPropertyValue(Object id) {
		// do nothing
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.ui.views.properties.IPropertySource#setPropertyValue(java
	 * .lang.Object, java.lang.Object)
	 */
	public void setPropertyValue(Object id, Object value) {
		// do nothing
	}

	/**
	 * DotTextPropertyDescriptor defines a read only TextPropertyDescriptor It
	 * also add a customised key/mouse listener. This listener will be useful
	 * for pinpointing the source.
	 * 
	 */
	private class DotTextPropertyDescriptor extends TextPropertyDescriptor {

		/**
		 * Constructor
		 * 
		 * @param id
		 *            Descriptor Id
		 * @param displayName
		 *            Descriptor display name
		 */
		public DotTextPropertyDescriptor(Object id, String displayName) {
			super(id, displayName);
		}

		/**
		 * create a Read Only TextCellEditor and add the defined listener to its
		 * Control.
		 * 
		 * @see org.eclipse.ui.views.properties.TextPropertyDescriptor#createPropertyEditor(org.eclipse.swt.widgets.Composite)
		 */
		@Override
		public CellEditor createPropertyEditor(Composite parent) {
			TextCellEditor editor = new SimpleTextCellEditor(parent,
					SWT.READ_ONLY);
			Text control = (Text) editor.getControl();
			control.setEditable(false);

			DotPropKeyMouseListener listener = new DotPropKeyMouseListener();
			control.addKeyListener(listener);
			control.addMouseListener(listener);
			if (getValidator() != null) {
				editor.setValidator(getValidator());
			}
			return editor;
		}
	}

	private class SimpleTextCellEditor extends TextCellEditor {
		public SimpleTextCellEditor(Composite parent, int readOnly) {
			super(parent, readOnly);
		}

		@Override
		public boolean isCopyEnabled() {
			return false;
		}

		@Override
		public boolean isCutEnabled() {
			return false;
		}

		@Override
		public boolean isPasteEnabled() {
			return false;
		}

		@Override
		public boolean isFindEnabled() {
			return false;
		}
	}

	/**
	 * This class is only for convenience for the moment. It's very likely that
	 * the listener will be provided by AnalyzeToolGraph
	 * 
	 */
	private class DotPropKeyMouseListener implements KeyListener, MouseListener {

		// Key

		public void keyPressed(org.eclipse.swt.events.KeyEvent e) {
			// do nothing by design
		}

		public void keyReleased(org.eclipse.swt.events.KeyEvent keyEvent) {
			// System.out.println("key released.");
			if (keyEvent.character == '\r') {
				String text = ((Text) keyEvent.getSource()).getText();
				if (text != null) {
					String[] segs = text.split(LINE_SEPARATOR);
					if (segs.length == 5) {
						openEditor(segs[2], segs[4]);
					}
				}
			}
		}

		// Mouse

		public void mouseDoubleClick(MouseEvent e) {
			String text = ((Text) e.getSource()).getText();
			if (text != null) {
				String[] segs = text.split(LINE_SEPARATOR);
				if (segs.length == 5) {
					openEditor(segs[2], segs[4]);
				}
			}
		}

		public void mouseDown(MouseEvent e) {
			// do nothing by design
		}

		public void mouseUp(MouseEvent e) {
			// do nothing by design
		}
	}

	/**
	 * Pinpoints one memory address to source code line.
	 * 
	 * @param memoryAddress
	 *            Memory address
	 * @param dllLoad
	 *            DllLoad item
	 * @return SourceFile if found otherwise null
	 */
	private SourceFile pinpoint(Long memoryAddress, DllLoad dllLoad) {

		if (dllLoad != null) {

			ISymbolFile symbolFile = iSymReader.getSymbolFile(
					dllLoad.getName(), false);
			if (symbolFile != null) {
				return pinpointToSrcLine(symbolFile, memoryAddress, dllLoad);
			}
		}
		return null;
	}

	/**
	 * Pinpoints memory address to source code line
	 * 
	 * @param symbolFile
	 *            Opened symbol file
	 * @param memoryAddress
	 *            Used memory address
	 * @param dllLoad
	 *            DllLoad object where to memory address belongs
	 * @return SourceFile
	 */
	private SourceFile pinpointToSrcLine(ISymbolFile symbolFile,
			Long memoryAddress, DllLoad dllLoad) {

		ICarbideProjectInfo cpi = CarbideBuilderPlugin.getBuildManager()
				.getProjectInfo(iCurrentProject);
		String platform = cpi.getDefaultConfiguration().getPlatformString();

		try {
			// this is the start address of each symbol file
			long defaultLinkAddress = 0;

			// if the platform is other than winscw => adjust the memory address
			if (!(Constants.BUILD_TARGET_WINSCW).equalsIgnoreCase(platform)) {
				defaultLinkAddress = 32768L;
			} else {
				defaultLinkAddress = -4096L;
			}

			// calculate memory address in symbol file
			long calculated = (memoryAddress - dllLoad.getStartAddress())
					+ defaultLinkAddress;

			java.math.BigInteger bigAddress = new java.math.BigInteger(Long
					.toHexString(calculated), 16);
			IFunction func = symbolFile.findFunctionByAddress(bigAddress);
			ISourceLocation loc = symbolFile.findSourceLocation(bigAddress);
			if (func != null && loc != null) {
				String sourceFile = loc.getSourceFile();
				if (sourceFile == null || sourceFile.equalsIgnoreCase("")) //$NON-NLS-1$
					return null;
				int lineNumber = loc.getLineNumber();
				if (lineNumber == 0)
					return null;
				String name = func.getName();
				if (name == null || name.equalsIgnoreCase("")) //$NON-NLS-1$
					return null;
				/*
				 * if( onlyForProjectFiles &&
				 * !isFilePartOfTheProject(loc.getSourceFile()) ) return null;
				 */
				SourceFile file = new SourceFile();
				file.setFileName(sourceFile);
				file.setLineNumber(lineNumber);
				file.setFunctionName(name);
				return file;
			}
		} catch (java.lang.NumberFormatException nfe) {
			// do nothing by design
			nfe.printStackTrace();
		} catch (Exception e) {
			// do nothing by design
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Opens current callstack item on default editor and pinpoints memory leak
	 * line
	 * 
	 * @param cppFileName
	 *            Cpp file name
	 * @param lineNumber
	 *            Cpp file line number
	 */
	private void openEditor(String cppFileName, String lineNumber) {

		// check that used information is given
		// we need to know file name and file line number
		// that we could open the right line in editor
		if (cppFileName == null || ("").equals(cppFileName) //$NON-NLS-1$
				|| lineNumber == null || ("").equals(lineNumber)) { //$NON-NLS-1$
			return;
		}
		try {
			IFile file = null;
			String usedFileName = null;
			usedFileName = getFileNames(cppFileName);
			if (iCurrentProject.isOpen()) {
				file = ResourcesPlugin.getWorkspace().getRoot().getFile(
						new Path(iCurrentProject.getName()
								+ usedFileName.toLowerCase(Locale.US)));
			}

			// if file not found in active project
			if (file == null || !file.exists()) {
				IWorkspaceRoot myWorkspaceRoot = ResourcesPlugin.getWorkspace()
						.getRoot();
				IProject[] projects = myWorkspaceRoot.getProjects();
				for (int i = 0; i < projects.length; i++) {
					file = ResourcesPlugin.getWorkspace().getRoot().getFile(
							new Path(projects[i].getName() + "\\" //$NON-NLS-1$
									+ usedFileName));

					// file found => skip the rest of the projects
					if (file != null && file.exists()) {
						break;
					}
				}
			}

			// if file still not found
			// display info to user and leave
			if (file == null || !file.exists()) {
				Util.showMessage(Constants.SOURCE_NOT_FOUND);
				return;
			}

			IWorkbenchPage page = PlatformUI.getWorkbench()
					.getActiveWorkbenchWindow().getActivePage();

			HashMap<String, Object> map = new HashMap<String, Object>();
			map.put(IMarker.LINE_NUMBER, Integer.parseInt(lineNumber));
			// map.put(IDE.EDITOR_ID_ATTR,
			// "org.eclipse.jdt.ui.ClassFileEditor");
			map.put(IDE.EDITOR_ID_ATTR, Constants.SOURCE_FILE_EDITOR_ID);
			IMarker marker = file.createMarker(IMarker.TEXT);
			marker.setAttributes(map);
			IDE.openEditor(page, marker, true);

		} catch (PartInitException pie) {
			pie.printStackTrace();
		} catch (NullPointerException npe) {
			npe.printStackTrace();
		} catch (CoreException ce) {
			ce.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Find symbol reader api pinpointed class file for project class files.
	 * 
	 * @param fileName
	 *            cpp file name
	 * @return found cpp file location
	 */
	private String getFileNames(String fileName) {
		int slash = Util.getLastSlashIndex(fileName);
		String tempFile = fileName.substring(slash + 1, fileName.length());

		String realFileName = fileName;
		for (String tempFileName : cppFileNames) {
			int slashTemp = Util.getLastSlashIndex(tempFileName);
			String tempFileWithoutExt = tempFileName.substring(slashTemp + 1,
					tempFileName.length());
			if (tempFileWithoutExt.equalsIgnoreCase(tempFile)) {
				realFileName = tempFileName;
				break;
			}
		}
		return realFileName;
	}
}