blob: b6c8705762244af3793ca9905fcde9a78a14c9b8 [file] [log] [blame]
/*******************************************************************************
* 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)
*******************************************************************************/
package org.eclipse.jdt.internal.ui.callhierarchy;
import java.lang.reflect.InvocationTargetException;
import org.eclipse.swt.widgets.Display;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.ui.progress.DeferredTreeContentManager;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.corext.callhierarchy.CallerMethodWrapper;
import org.eclipse.jdt.internal.corext.callhierarchy.MethodCall;
import org.eclipse.jdt.internal.corext.callhierarchy.MethodWrapper;
import org.eclipse.jdt.internal.corext.callhierarchy.RealCallers;
import org.eclipse.jdt.internal.corext.refactoring.util.JavaElementUtil;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.util.ExceptionHandler;
public class CallHierarchyContentProvider implements ITreeContentProvider {
/**
* A named preference that holds the types whose methods are by default expanded with
* constructors in the Call Hierarchy.
* <p>
* Value is of type <code>String</code>: semicolon separated list of fully qualified type names.
* <strong> It has been replaced in 3.6 by API:
* {@link PreferenceConstants#PREF_DEFAULT_EXPAND_WITH_CONSTRUCTORS_MEMBERS}</strong>
* </p>
*
* @since 3.5
*/
public static final String OLD_PREF_DEFAULT_EXPAND_WITH_CONSTRUCTORS= "CallHierarchy.defaultExpandWithConstructors"; //$NON-NLS-1$
private final static Object[] EMPTY_ARRAY= new Object[0];
private DeferredTreeContentManager fManager;
private CallHierarchyViewPart fPart;
private class MethodWrapperRunnable implements IRunnableWithProgress {
private MethodWrapper fMethodWrapper;
private MethodWrapper[] fCalls= null;
MethodWrapperRunnable(MethodWrapper methodWrapper) {
fMethodWrapper= methodWrapper;
}
@Override
public void run(IProgressMonitor pm) {
fCalls= fMethodWrapper.getCalls(pm);
}
MethodWrapper[] getCalls() {
if (fCalls != null) {
return fCalls;
}
return new MethodWrapper[0];
}
}
public CallHierarchyContentProvider(CallHierarchyViewPart part) {
super();
fPart= part;
}
/**
* @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
*/
@Override
public Object[] getChildren(Object parentElement) {
if (parentElement instanceof TreeRoot) {
TreeRoot dummyRoot= (TreeRoot)parentElement;
return dummyRoot.getRoots();
} else if (parentElement instanceof RealCallers) {
MethodWrapper parentWrapper= ((RealCallers)parentElement).getParent();
RealCallers element= ((RealCallers)parentElement);
if (fManager != null) {
Object[] children= fManager.getChildren(new DeferredMethodWrapper(this, element));
if (children != null)
return children;
}
return fetchChildren(parentWrapper);
} else if (parentElement instanceof MethodWrapper) {
MethodWrapper methodWrapper= ((MethodWrapper)parentElement);
if (shouldStopTraversion(methodWrapper)) {
return EMPTY_ARRAY;
} else {
if (parentElement instanceof CallerMethodWrapper) {
CallerMethodWrapper caller= (CallerMethodWrapper)parentElement;
ensureDefaultExpandWithConstructors(caller);
if (caller.getExpandWithConstructors()) {
IType type= caller.getMember().getDeclaringType();
try {
if (type.isAnonymous()) {
IMember anonymousClass= type;
MethodCall anonymousConstructor= new MethodCall(anonymousClass);
CallerMethodWrapper anonymousWrapper= (CallerMethodWrapper)caller.createMethodWrapper(anonymousConstructor);
return new Object[] { anonymousWrapper, new RealCallers(methodWrapper, caller.getMethodCall()) };
} else {
IMember[] constructors= JavaElementUtil.getAllConstructors(type);
if (constructors.length == 0) {
constructors= new IType[] { type }; // type stands for the default constructor
}
Object children[]= new Object[constructors.length + 1];
for (int j= 0; j < constructors.length; j++) {
MethodCall constructor= new MethodCall(constructors[j]);
CallerMethodWrapper constructorWrapper= (CallerMethodWrapper)caller.createMethodWrapper(constructor);
children[j]= constructorWrapper;
}
children[constructors.length]= new RealCallers(methodWrapper, caller.getMethodCall());
return children;
}
} catch (JavaModelException e) {
JavaPlugin.log(e);
return null;
}
}
}
if (fManager != null) {
Object[] children= fManager.getChildren(new DeferredMethodWrapper(this, methodWrapper));
if (children != null)
return children;
}
return fetchChildren(methodWrapper);
}
}
return EMPTY_ARRAY;
}
/**
* Sets the default "expand with constructors" mode for the method wrapper. Does nothing if the
* mode has already been set.
*
*
* @param wrapper the caller method wrapper
* @since 3.5
*/
static void ensureDefaultExpandWithConstructors(CallerMethodWrapper wrapper) {
if (!wrapper.isExpandWithConstructorsSet()) {
if (CallHierarchyContentProvider.canExpandWithConstructors(wrapper)) {
IMethod method= (IMethod)wrapper.getMember();
IType type= method.getDeclaringType();
try {
boolean withConstructors= false;
if (type != null) {
boolean anonymousPref= PreferenceConstants.getPreferenceStore().getBoolean(PreferenceConstants.PREF_ANONYMOUS_EXPAND_WITH_CONSTRUCTORS);
if (anonymousPref && type.isAnonymous()) {
withConstructors= true;
} else if (isInTheDefaultExpandWithConstructorList(method)) {
withConstructors= true;
}
}
wrapper.setExpandWithConstructors(withConstructors);
} catch (JavaModelException e) {
// ignore: expand mode will be off
}
}
}
}
/**
* Checks whether given caller method wrapper can be expanded with constructors.
*
* @param wrapper the caller method wrapper
* @return <code> true</code> if the wrapper can be expanded with constructors, <code>false</code> otherwise
* @since 3.5
*/
static boolean canExpandWithConstructors(CallerMethodWrapper wrapper) {
IMember member= wrapper.getMember();
if (!(member instanceof IMethod))
return false;
IMethod method= (IMethod)member;
try {
if (JdtFlags.isStatic(method) || method.isConstructor())
return false;
} catch (JavaModelException e) {
return false; // don't try to work with inexistent elements
}
return true;
}
/**
* Checks if the method or its declaring type matches the pre-defined array of methods and types
* for default expand with constructors.
*
* @param method the wrapped method
* @return <code>true</code> if method or type matches the pre-defined list, <code>false</code>
* otherwise
*
* @since 3.5
*/
static boolean isInTheDefaultExpandWithConstructorList(IMethod method) {
String serializedMembers= PreferenceConstants.getPreferenceStore().getString(PreferenceConstants.PREF_DEFAULT_EXPAND_WITH_CONSTRUCTORS_MEMBERS);
if (serializedMembers.length() == 0)
return false;
String[] defaultMemberPatterns= serializedMembers.split(";"); //$NON-NLS-1$
String methodName= method.getElementName();
IType declaringType= method.getDeclaringType();
String declaringTypeName= declaringType.getFullyQualifiedName('.');
String superClassName;
String[] superInterfaceNames;
try {
superClassName= declaringType.getSuperclassName();
if (superClassName != null) {
superClassName= stripTypeArguments(superClassName);
}
superInterfaceNames= declaringType.getSuperInterfaceNames();
for (int i= 0; i < superInterfaceNames.length; i++) {
superInterfaceNames[i]= stripTypeArguments(superInterfaceNames[i]);
}
} catch (JavaModelException e) {
return false;
}
for (String defaultMemberPattern : defaultMemberPatterns) {
int pos= defaultMemberPattern.lastIndexOf('.');
String defaultTypeName= defaultMemberPattern.substring(0, pos);
String defaultMethodName= defaultMemberPattern.substring(pos + 1);
if ("*".equals(defaultMethodName)) { //$NON-NLS-1$
if (declaringTypeName.equals(defaultTypeName)) {
return true;
}
} else {
if (!methodName.equals(defaultMethodName)) {
continue;
}
if (declaringTypeName.equals(defaultTypeName)) {
return true;
}
}
if (superClassName != null && JavaModelUtil.isMatchingName(superClassName, defaultTypeName)) {
return true;
}
for (String superInterfaceName : superInterfaceNames) {
if (JavaModelUtil.isMatchingName(superInterfaceName, defaultTypeName)) {
return true;
}
}
}
return false;
}
/**
* Strips type arguments from the given type name and returns only erased type name.
*
* @param typeName the type name
* @return the erased type name
*
* @since 3.6
*/
private static String stripTypeArguments(String typeName) {
int pos= typeName.indexOf('<');
if (pos != -1)
return typeName.substring(0, pos);
return typeName;
}
protected Object[] fetchChildren(final MethodWrapper methodWrapper) {
IRunnableContext context= JavaPlugin.getActiveWorkbenchWindow();
MethodWrapperRunnable runnable= new MethodWrapperRunnable(methodWrapper);
try {
context.run(true, true, runnable);
} catch (InvocationTargetException e) {
ExceptionHandler.handle(e, CallHierarchyMessages.CallHierarchyContentProvider_searchError_title, CallHierarchyMessages.CallHierarchyContentProvider_searchError_message);
return EMPTY_ARRAY;
} catch (InterruptedException e) {
final CallerMethodWrapper element= (CallerMethodWrapper)methodWrapper;
if (!isExpandWithConstructors(element)) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
collapseAndRefresh(element);
}
});
}
}
return runnable.getCalls();
}
/**
* Returns whether the given element is an "Expand witch Constructors" node.
*
* @param element a method wrapped
* @return <code>true</code> iff the element is an "Expand witch Constructors" node
* @since 3.5
*/
static boolean isExpandWithConstructors(MethodWrapper element) {
return element instanceof CallerMethodWrapper && ((CallerMethodWrapper)element).getExpandWithConstructors();
}
/**
* Collapses and refreshes the given element when search has been canceled.
*
* @param element the element on which search has been canceled and which has to be collapsed
* @since 3.5
*/
protected void collapseAndRefresh(MethodWrapper element) {
CallHierarchyViewer viewer= fPart.getViewer();
/* Problem case: The user expands the RealCallers node and then unchecks "Expand with Constructors"
* while the search for the real callers is still in progress.
*
* In this scenario, the RealCallers is not even part of the current tree any more, since the
* ExpandWithConstructorsAction already toggled the flag and refreshed the tree.
*
* But since setExpandedState(element, false) walks up the getParent() chain of the given element,
* this causes the parent's children to be created, which would wrongly start a deferred search.
*
* The fix is to do nothing when the RealCaller's parent is expandWithConstructors.
*/
boolean elementStays= true;
if (element instanceof RealCallers) {
elementStays= isExpandWithConstructors(element.getParent());
}
if (elementStays) {
viewer.setExpandedState(element, false);
}
viewer.refresh(element);
}
/**
* Returns the call hierarchy view part.
*
* @return the call hierarchy view part
* @since 3.5
*/
public CallHierarchyViewPart getViewPart() {
return fPart;
}
private boolean shouldStopTraversion(MethodWrapper methodWrapper) {
return (methodWrapper.getLevel() > CallHierarchyUI.getDefault().getMaxCallDepth()) || methodWrapper.isRecursive();
}
/**
* @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
*/
@Override
public Object[] getElements(Object inputElement) {
return getChildren(inputElement);
}
/**
* @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
*/
@Override
public Object getParent(Object element) {
if (element instanceof MethodWrapper) {
return ((MethodWrapper) element).getParent();
}
return null;
}
/**
* @see org.eclipse.jface.viewers.IContentProvider#dispose()
*/
@Override
public void dispose() {
// Nothing to dispose
}
/**
* @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
*/
@Override
public boolean hasChildren(Object element) {
if (element == TreeRoot.EMPTY_ROOT || element == TreeTermination.SEARCH_CANCELED) {
return false;
}
// Only certain members can have subelements, so there's no need to fool the
// user into believing that there is more
if (element instanceof MethodWrapper) {
MethodWrapper methodWrapper= (MethodWrapper) element;
if (! methodWrapper.canHaveChildren()) {
return false;
}
if (shouldStopTraversion(methodWrapper)) {
return false;
}
return true;
} else if (element instanceof TreeRoot) {
return true;
} else if (element instanceof DeferredMethodWrapper) {
// Err on the safe side by returning true even though
// we don't know for sure that there are children.
return true;
}
return false; // the "Update ..." placeholder has no children
}
/**
* @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer,
* java.lang.Object, java.lang.Object)
*/
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
if (oldInput instanceof TreeRoot) {
MethodWrapper[] roots = ((TreeRoot) oldInput).getRoots();
cancelJobs(roots);
}
if (viewer instanceof AbstractTreeViewer) {
fManager = new DeferredTreeContentManager((AbstractTreeViewer) viewer, fPart.getSite());
}
}
/**
* Cancel all current jobs.
* @param wrappers the parents to cancel jobs for
*/
void cancelJobs(MethodWrapper[] wrappers) {
if (fManager != null && wrappers != null) {
for (MethodWrapper wrapper : wrappers) {
fManager.cancel(wrapper);
}
if (fPart != null) {
fPart.setCancelEnabled(false);
}
}
}
/**
*
*/
public void doneFetching() {
if (fPart != null) {
fPart.setCancelEnabled(false);
}
}
/**
*
*/
public void startFetching() {
if (fPart != null) {
fPart.setCancelEnabled(true);
}
}
}