| /******************************************************************************* |
| * Copyright (c) 2000, 2012 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Jesper Kamstrup Linnet (eclipse@kamstrup-linnet.dk) - initial API and implementation |
| * (report 36180: Callers/Callees view) |
| * Stephan Herrmann (stephan@cs.tu-berlin.de): |
| * - bug 75800: [call hierarchy] should allow searches for fields |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.ui.callhierarchy; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.swt.widgets.Shell; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| |
| import org.eclipse.jface.dialogs.ErrorDialog; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| |
| import org.eclipse.ui.IEditorPart; |
| import org.eclipse.ui.IViewReference; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.PartInitException; |
| |
| import org.eclipse.ui.texteditor.ITextEditor; |
| |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IMember; |
| import org.eclipse.jdt.core.ISourceRange; |
| import org.eclipse.jdt.core.JavaModelException; |
| |
| import org.eclipse.jdt.internal.corext.callhierarchy.CallHierarchy; |
| import org.eclipse.jdt.internal.corext.callhierarchy.CallLocation; |
| import org.eclipse.jdt.internal.corext.callhierarchy.MethodWrapper; |
| import org.eclipse.jdt.internal.corext.util.Messages; |
| |
| import org.eclipse.jdt.ui.JavaUI; |
| |
| import org.eclipse.jdt.internal.ui.IJavaStatusConstants; |
| import org.eclipse.jdt.internal.ui.JavaPlugin; |
| import org.eclipse.jdt.internal.ui.actions.SelectionConverter; |
| import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; |
| import org.eclipse.jdt.internal.ui.util.ExceptionHandler; |
| |
| public class CallHierarchyUI { |
| private static final int DEFAULT_MAX_CALL_DEPTH= 10; |
| private static final String PREF_MAX_CALL_DEPTH = "PREF_MAX_CALL_DEPTH"; //$NON-NLS-1$ |
| |
| private static CallHierarchyUI fgInstance; |
| private int fViewCount= 0; |
| private final List<IMember[]> fMethodHistory= new ArrayList<>(); |
| |
| /** |
| * List of the Call Hierarchy views in LRU order, where the most recently used view is at index 0. |
| * @since 3.7 |
| */ |
| private List<CallHierarchyViewPart> fLRUCallHierarchyViews= new ArrayList<>(); |
| |
| private CallHierarchyUI() { |
| // Do nothing |
| } |
| |
| public static CallHierarchyUI getDefault() { |
| if (fgInstance == null) { |
| fgInstance = new CallHierarchyUI(); |
| } |
| |
| return fgInstance; |
| } |
| |
| /** |
| * Returns the maximum tree level allowed |
| * @return int |
| */ |
| public int getMaxCallDepth() { |
| int maxCallDepth; |
| |
| IPreferenceStore settings = JavaPlugin.getDefault().getPreferenceStore(); |
| maxCallDepth = settings.getInt(PREF_MAX_CALL_DEPTH); |
| if (maxCallDepth < 1 || maxCallDepth > 99) { |
| maxCallDepth= DEFAULT_MAX_CALL_DEPTH; |
| } |
| |
| return maxCallDepth; |
| } |
| |
| public void setMaxCallDepth(int maxCallDepth) { |
| IPreferenceStore settings = JavaPlugin.getDefault().getPreferenceStore(); |
| settings.setValue(PREF_MAX_CALL_DEPTH, maxCallDepth); |
| } |
| |
| public static void jumpToMember(IJavaElement element) { |
| if (element != null) { |
| try { |
| JavaUI.openInEditor(element, true, true); |
| } catch (JavaModelException e) { |
| JavaPlugin.log(e); |
| } catch (PartInitException e) { |
| JavaPlugin.log(e); |
| } |
| } |
| } |
| |
| public static void jumpToLocation(CallLocation callLocation) { |
| try { |
| IEditorPart methodEditor = JavaUI.openInEditor(callLocation.getMember(), false, false); |
| if (methodEditor instanceof ITextEditor) { |
| ITextEditor editor = (ITextEditor) methodEditor; |
| editor.selectAndReveal(callLocation.getStart(), |
| (callLocation.getEnd() - callLocation.getStart())); |
| } |
| } catch (JavaModelException e) { |
| JavaPlugin.log(e); |
| } catch (PartInitException e) { |
| JavaPlugin.log(e); |
| } |
| } |
| |
| /** |
| * Opens the element in the editor or shows an error dialog if that fails. |
| * |
| * @param element the element to open |
| * @param shell parent shell for error dialog |
| * @param activateOnOpen <code>true</code> if the editor should be activated |
| * @return <code>true</code> iff no error occurred while trying to open the editor, |
| * <code>false</code> iff an error dialog was raised. |
| */ |
| public static boolean openInEditor(Object element, Shell shell, boolean activateOnOpen) { |
| CallLocation callLocation= CallHierarchy.getCallLocation(element); |
| |
| try { |
| IMember enclosingMember; |
| int selectionStart; |
| int selectionLength; |
| |
| if (callLocation != null) { |
| enclosingMember= callLocation.getMember(); |
| selectionStart= callLocation.getStart(); |
| selectionLength= callLocation.getEnd() - selectionStart; |
| } else if (element instanceof MethodWrapper) { |
| enclosingMember= ((MethodWrapper) element).getMember(); |
| ISourceRange selectionRange= enclosingMember.getNameRange(); |
| if (selectionRange == null) |
| selectionRange= enclosingMember.getSourceRange(); |
| if (selectionRange == null) |
| return true; |
| selectionStart= selectionRange.getOffset(); |
| selectionLength= selectionRange.getLength(); |
| } else { |
| return true; |
| } |
| |
| IEditorPart methodEditor = JavaUI.openInEditor(enclosingMember, activateOnOpen, false); |
| if (methodEditor instanceof ITextEditor) { |
| ITextEditor editor = (ITextEditor) methodEditor; |
| editor.selectAndReveal(selectionStart, selectionLength); |
| } |
| return true; |
| } catch (JavaModelException e) { |
| JavaPlugin.log(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), |
| IJavaStatusConstants.INTERNAL_ERROR, |
| CallHierarchyMessages.CallHierarchyUI_open_in_editor_error_message, e)); |
| |
| ErrorDialog.openError(shell, CallHierarchyMessages.OpenLocationAction_error_title, |
| CallHierarchyMessages.CallHierarchyUI_open_in_editor_error_message, |
| e.getStatus()); |
| return false; |
| } catch (PartInitException x) { |
| String name; |
| if (callLocation != null) |
| name= callLocation.getCalledMember().getElementName(); |
| else if (element instanceof MethodWrapper) |
| name= ((MethodWrapper) element).getName(); |
| else |
| name= ""; //$NON-NLS-1$ |
| MessageDialog.openError(shell, CallHierarchyMessages.OpenLocationAction_error_title, |
| Messages.format( |
| CallHierarchyMessages.CallHierarchyUI_open_in_editor_error_messageArgs, |
| new String[] { name, x.getMessage() })); |
| return false; |
| } |
| } |
| |
| public static IEditorPart isOpenInEditor(Object elem) { |
| IJavaElement javaElement= null; |
| if (elem instanceof MethodWrapper) { |
| javaElement= ((MethodWrapper) elem).getMember(); |
| } else if (elem instanceof CallLocation) { |
| javaElement= ((CallLocation) elem).getMember(); |
| } |
| if (javaElement != null) { |
| return EditorUtility.isOpenInEditor(javaElement); |
| } |
| return null; |
| } |
| |
| public static CallHierarchyViewPart openSelectionDialog(IMember[] candidates, IWorkbenchWindow window) { |
| Assert.isTrue(candidates != null); |
| |
| IMember input= null; |
| if (candidates.length > 1) { |
| String title= CallHierarchyMessages.CallHierarchyUI_selectionDialog_title; |
| String message= CallHierarchyMessages.CallHierarchyUI_selectionDialog_message; |
| input= (IMember) SelectionConverter.selectJavaElement(candidates, window.getShell(), title, message); |
| } else if (candidates.length == 1) { |
| input= candidates[0]; |
| } |
| if (input == null) |
| return openView(new IMember[] {}, window); |
| |
| return openView(new IMember[] { input }, window); |
| } |
| |
| public static CallHierarchyViewPart openView(IMember[] input, IWorkbenchWindow window) { |
| if (input.length == 0) { |
| MessageDialog.openInformation(window.getShell(), CallHierarchyMessages.CallHierarchyUI_selectionDialog_title, |
| CallHierarchyMessages.CallHierarchyUI_open_operation_unavialable); |
| return null; |
| } |
| IWorkbenchPage page= window.getActivePage(); |
| try { |
| CallHierarchyViewPart viewPart= getDefault().findLRUCallHierarchyViewPart(page); //find the first view which is not pinned |
| String secondaryId= null; |
| if (viewPart == null) { |
| if (page.findViewReference(CallHierarchyViewPart.ID_CALL_HIERARCHY) != null) //all the current views are pinned, open a new instance |
| secondaryId= String.valueOf(++getDefault().fViewCount); |
| } else |
| secondaryId= viewPart.getViewSite().getSecondaryId(); |
| viewPart= (CallHierarchyViewPart)page.showView(CallHierarchyViewPart.ID_CALL_HIERARCHY, secondaryId, IWorkbenchPage.VIEW_ACTIVATE); |
| viewPart.setInputElements(input); |
| return viewPart; |
| } catch (CoreException e) { |
| ExceptionHandler.handle(e, window.getShell(), |
| CallHierarchyMessages.CallHierarchyUI_error_open_view, e.getMessage()); |
| } |
| return null; |
| } |
| |
| /** |
| * Finds the first Call Hierarchy view part instance that is not pinned. |
| * |
| * @param page the active page |
| * @return the Call Hierarchy view part to open or <code>null</code> if none found |
| * @since 3.7 |
| */ |
| private CallHierarchyViewPart findLRUCallHierarchyViewPart(IWorkbenchPage page) { |
| boolean viewFoundInPage= false; |
| for (CallHierarchyViewPart view : fLRUCallHierarchyViews) { |
| if (page.equals(view.getSite().getPage())) { |
| if (!view.isPinned()) { |
| return view; |
| } |
| viewFoundInPage= true; |
| } |
| } |
| if (!viewFoundInPage) { |
| // find unresolved views |
| for (IViewReference curr : page.getViewReferences()) { |
| if (CallHierarchyViewPart.ID_CALL_HIERARCHY.equals(curr.getId()) && page.equals(curr.getPage())) { |
| CallHierarchyViewPart view= (CallHierarchyViewPart)curr.getView(true); |
| if (view != null && !view.isPinned()) { |
| return view; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Adds the activated view part to the head of the list. |
| * |
| * @param view the Call Hierarchy view part |
| * @since 3.7 |
| */ |
| void callHierarchyViewActivated(CallHierarchyViewPart view) { |
| fLRUCallHierarchyViews.remove(view); |
| fLRUCallHierarchyViews.add(0, view); |
| } |
| |
| /** |
| * Removes the closed view part from the list. |
| * |
| * @param view the closed view part |
| * @since 3.7 |
| */ |
| void callHierarchyViewClosed(CallHierarchyViewPart view) { |
| fLRUCallHierarchyViews.remove(view); |
| } |
| |
| /** |
| * Converts an ISelection (containing MethodWrapper instances) to an ISelection |
| * with the MethodWrapper's replaced by their corresponding IMembers. If the selection |
| * contains elements which are not MethodWrapper instances or not already IMember instances |
| * they are discarded. |
| * @param selection The selection to convert. |
| * @return An ISelection containing IMember's in place of MethodWrapper instances. |
| */ |
| static ISelection convertSelection(ISelection selection) { |
| if (selection.isEmpty()) { |
| return selection; |
| } |
| |
| if (selection instanceof IStructuredSelection) { |
| IStructuredSelection structuredSelection= (IStructuredSelection) selection; |
| List<IMember> javaElements= new ArrayList<>(); |
| for (Iterator<?> iter= structuredSelection.iterator(); iter.hasNext();) { |
| Object element= iter.next(); |
| if (element instanceof MethodWrapper) { |
| IMember member= ((MethodWrapper)element).getMember(); |
| if (member != null) { |
| javaElements.add(member); |
| } |
| } else if (element instanceof IMember) { |
| javaElements.add((IMember) element); |
| } else if (element instanceof CallLocation) { |
| IMember member = ((CallLocation) element).getMember(); |
| javaElements.add(member); |
| } |
| } |
| return new StructuredSelection(javaElements); |
| } |
| return StructuredSelection.EMPTY; |
| } |
| |
| /** |
| * Clears the history and updates all the open views. |
| * |
| * @since 3.7 |
| */ |
| void clearHistory() { |
| for (CallHierarchyViewPart part : fLRUCallHierarchyViews) { |
| part.setHistoryEntries(new IMember[0][]); |
| part.setInputElements(null); |
| } |
| } |
| |
| /** |
| * Returns the method history. |
| * |
| * @return the method history |
| * @since 3.7 |
| */ |
| List<IMember[]> getMethodHistory() { |
| return fMethodHistory; |
| } |
| } |