sysperfana/analyzetoolext/com.nokia.s60tools.analyzetool/src/com/nokia/s60tools/analyzetool/internal/ui/graph/MemOpDescriptor.java
author Matti Laitinen <matti.t.laitinen@nokia.com>
Wed, 21 Apr 2010 19:42:48 +0300
changeset 6 f65f740e69f9
parent 1 1050670c6980
child 15 0367d2db2c06
permissions -rw-r--r--
AnalyzeTool Carbide Extension v1.9.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);

	/** 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);
		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 (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;
	}
}