blob: 71d74f73aa194a4b37b1cc45f2789e032e8d74b3 [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Development Tooling"-Software
*
* Copyright 2006, 2007 Technical University Berlin, Germany 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
*
* Please visit http://www.eclipse.org/objectteams for updates and contact.
*
* Contributors:
* Technical University Berlin - Initial API and implementation
* IBM Corporation - copies of individual methods from bound base classes.
**********************************************************************/
package org.eclipse.objectteams.otdt.internal.corext;
import static org.eclipse.jdt.internal.core.search.matching.MethodPattern.findingCallers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IJavaElement;
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.core.Signature;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.internal.corext.callhierarchy.MethodCall;
import org.eclipse.jdt.internal.corext.util.SearchUtils;
import org.eclipse.objectteams.otdt.core.ICallinMapping;
import org.eclipse.objectteams.otdt.core.ICalloutMapping;
import org.eclipse.objectteams.otdt.core.IMethodMapping;
import org.eclipse.objectteams.otdt.core.IOTJavaElement;
import org.eclipse.objectteams.otdt.core.IOTType;
import org.eclipse.objectteams.otdt.core.IRoleType;
import org.eclipse.objectteams.otdt.core.OTModelManager;
import org.eclipse.objectteams.otdt.core.compiler.IOTConstants;
import org.eclipse.objectteams.otdt.core.compiler.OTNameUtils;
import org.eclipse.objectteams.otdt.internal.core.compiler.ast.CallinMappingDeclaration;
import base org.eclipse.jdt.internal.corext.callhierarchy.CallSearchResultCollector;
import base org.eclipse.jdt.internal.corext.callhierarchy.CalleeMethodWrapper;
import base org.eclipse.jdt.internal.corext.callhierarchy.CallerMethodWrapper;
import base org.eclipse.jdt.internal.corext.callhierarchy.MethodReferencesSearchRequestor;
import base org.eclipse.jdt.internal.corext.callhierarchy.MethodWrapper;
import base org.eclipse.jdt.internal.ui.callhierarchy.CallHierarchyContentProvider;
/**
* This class allows the call hierarchy to cope with callin/callout method mappings.
*
* @author stephan
*/
@SuppressWarnings({"restriction", "decapsulation"})
public team class CallHierarchyAdaptor
{
/**
* Purpose: accept method mappings, too.
*/
protected class MappingReferenceSearchRequestor playedBy MethodReferencesSearchRequestor
{
protected MappingReferenceSearchRequestor() {
base();
}
acceptSearchMatch <- replace acceptSearchMatch;
@SuppressWarnings("basecall")
callin void acceptSearchMatch(SearchMatch match) {
// COPY&PASTE from base version
if (getRequireExactMatch() && (match.getAccuracy() != SearchMatch.A_ACCURATE)) {
return;
}
if (match.isInsideDocComment()) {
return;
}
if (match.getElement() != null && match.getElement() instanceof IMember) {
IMember member= (IMember) match.getElement();
switch (member.getElementType()) {
case IJavaElement.METHOD:
case IJavaElement.TYPE:
case IJavaElement.FIELD:
case IJavaElement.INITIALIZER:
//{ObjectTeams: the payload:
case IOTJavaElement.CALLIN_MAPPING:
case IOTJavaElement.CALLOUT_MAPPING:
case IOTJavaElement.CALLOUT_TO_FIELD_MAPPING:
// SH}
getSearchResults().addMember(member, member, match.getOffset(), match.getOffset()+match.getLength());
break;
}
}
}
boolean getRequireExactMatch() -> get boolean fRequireExactMatch;
CallSearchResultCollector getSearchResults() -> get CallSearchResultCollector fSearchResults;
Map<String, MethodCall> getCallers() -> Map<String, MethodCall> getCallers();
}
/**
* Count method mappings as legal children, too.
*/
protected class ContentProvider playedBy CallHierarchyContentProvider
{
@SuppressWarnings("hidden-lifting-problem") // lifting 'wrapper' could theoretically fail
boolean hasChildren(MethodMappingWrapper wrapper) <- replace boolean hasChildren(Object element)
base when (element instanceof MethodWrapper)
with { wrapper <- (MethodWrapper)element }
callin boolean hasChildren(MethodMappingWrapper methodWrapper) {
if (base.hasChildren(methodWrapper))
return true;
switch (methodWrapper.getMember().getElementType()) {
case IOTJavaElement.CALLIN_MAPPING:
case IOTJavaElement.CALLOUT_MAPPING:
case IOTJavaElement.CALLOUT_TO_FIELD_MAPPING:
break; // continue below
default:
return false;
}
if (shouldStopTraversion(methodWrapper)) {
return false;
}
return true;
}
boolean shouldStopTraversion(MethodMappingWrapper methodWrapper) -> boolean shouldStopTraversion(MethodWrapper methodWrapper);
}
/**
* Implement new strategies for finding children of method mappings. General part.
*/
@SuppressWarnings("abstractrelevantrole") // MethodWrapper is abstract, both sub classes are bound individually
abstract protected class MethodMappingWrapper playedBy MethodWrapper
{
@SuppressWarnings("hidden-lifting-problem") // abstract role could potentially throw LiftingFailedException
doFindChildren <- replace doFindChildren;
@SuppressWarnings("basecall")
callin void doFindChildren(IProgressMonitor progressMonitor) {
IMember member = getMember();
IMember[] children = null;
if ( member.getElementType() == IOTJavaElement.CALLIN_MAPPING
|| member.getElementType() == IOTJavaElement.CALLOUT_MAPPING)
{
Map<String,MethodCall> existingResults = lookupMethod(getMethodCall());
if (existingResults != null && !existingResults.isEmpty()) {
Map<String,MethodCall> newElements = new HashMap<String,MethodCall>();
setElementMap(newElements);
newElements.putAll(existingResults);
return; // successfully done
} else {
// try direct lookup instead of searching:
try {
children = (member.getElementType() == IOTJavaElement.CALLIN_MAPPING) ?
getCallinMethods((ICallinMapping)member) :
getCalloutMethods((ICalloutMapping)member);
} catch (JavaModelException e) {
return; // search failed
}
}
} else if (isGuardPredicate(member)) {
// directly fetch affected callins without search
children = getCallinsAffectedByGuard(member);
}
if (children != null) {
// if found add result:
addReferencedMembers(children);
return;
}
if (handleRoleInstantiations(member, progressMonitor))
return;
// nothing-found scenarii, revert to normal behavior:
base.doFindChildren(progressMonitor);
}
private void addReferencedMembers(IMember[] children) {
initCalls(); // orig from doFindChildren()
Map<String, MethodCall> newElements = getFElements();
for (IMember method : children) {
MethodCall methodCall = new MethodCall(method);
addCallToCache(methodCall); // see performSearch()
newElements.put(method.getHandleIdentifier(), methodCall);
}
}
private boolean isGuardPredicate(IMember member) {
if (member.getElementType() != IJavaElement.METHOD)
return false;
String selector = member.getElementName();
return OTNameUtils.isPredicate(selector.toCharArray());
}
// hooks:
abstract IMethod[] getCallinMethods(ICallinMapping callinMapping) throws JavaModelException;
abstract IMethod[] getCalloutMethods(ICalloutMapping calloutMapping) throws JavaModelException;
IMember[] getCallinsAffectedByGuard(IMember member) {
/* default: */ return null;
}
boolean handleRoleInstantiations(IMember member, IProgressMonitor progressMonitor) {
/* default: */ return false;
}
// callouts:
void setElementMap(Map<String,MethodCall> map)-> set Map<String,MethodCall> fElements;
protected Map<String, MethodCall> getFElements() -> get Map<String,MethodCall> fElements;
void setMethodCall(MethodCall methodCall) -> set MethodCall fMethodCall;
protected void initCalls() -> void initCalls();
IMember getMember() -> IMember getMember();
MethodCall getMethodCall() -> MethodCall getMethodCall();
Map<String,MethodCall> lookupMethod(MethodCall methodCall)
-> Map<String,MethodCall> lookupMethod(MethodCall methodCall);
protected void addCallToCache(MethodCall methodCall) -> void addCallToCache(MethodCall methodCall);
}
/**
* Special case method wrapper: Callee direction. Relies on smart lifting.
* FIXME(SH): This class is mainly untested yet.
* Find out how the original visitors work and how they must be adapted.
*/
protected class CalleeMethodMappingWrapper
extends MethodMappingWrapper
playedBy CalleeMethodWrapper
{
@Override
public IMethod[] getCallinMethods(ICallinMapping callinMapping)
throws JavaModelException
{
return new IMethod[] {callinMapping.getRoleMethod()};
}
@Override
public IMethod[] getCalloutMethods(ICalloutMapping calloutMapping)
throws JavaModelException
{
return new IMethod[] {calloutMapping.getBoundBaseMethod()};
}
}
/**
* Special case method wrapper: Caller direction. Relies on smart lifting.
*/
protected class CallerMethodMappingWrapper
extends MethodMappingWrapper
playedBy CallerMethodWrapper
{
IJavaSearchScope getAccurateSearchScope(IJavaSearchScope defaultSearchScope, IMember member)
-> IJavaSearchScope getAccurateSearchScope(IJavaSearchScope defaultSearchScope, IMember arg1);
IJavaSearchScope getSearchScope() -> IJavaSearchScope getSearchScope();
@Override
public IMethod[] getCallinMethods(ICallinMapping callinMapping)
throws JavaModelException
{
// no need for searching: our callers are explicitly listed here:
return callinMapping.getBoundBaseMethods();
}
@Override
public IMethod[] getCalloutMethods(ICalloutMapping calloutMapping)
throws JavaModelException
{
return null; // calloutMapping.getRoleMethod() is not useful, revert to normal searching.
}
/**
* Investigate predicate method name and collect all callins
* affected by this guard.
*/
@Override
IMember[] getCallinsAffectedByGuard(IMember member) {
IType declaringClass = member.getDeclaringType();
String suffix = null; // what comes after _OT$when or _OT$base_when
String selector = member.getElementName();
if (selector.startsWith(String.valueOf(IOTConstants.PREDICATE_METHOD_NAME))) {
// regular guard
if (selector.length() == IOTConstants.PREDICATE_METHOD_NAME.length) {
// no suffix
} else {
suffix = selector.substring(IOTConstants.PREDICATE_METHOD_NAME.length);
}
} else if (selector.equals(String.valueOf(IOTConstants.BASE_PREDICATE_PREFIX))) {
// base guard
if (selector.length() == IOTConstants.BASE_PREDICATE_PREFIX.length) {
// no suffix
} else {
suffix = selector.substring(IOTConstants.BASE_PREDICATE_PREFIX.length);
}
}
if (suffix == null) {
// class level predicate
IOTType otType = OTModelManager.getOTElement(declaringClass);
ArrayList<IMember> result = new ArrayList<IMember> ();
if (otType.isTeam()) {
try {
IType[] roles = otType.getRoleTypes();
for (IType type : roles) {
IRoleType roleType = (IRoleType) OTModelManager.getOTElement(type);
for (IMember m : roleType.getMethodMappings(IRoleType.CALLINS))
result.add(m);
}
} catch (JavaModelException jme) {
// no success retrieving roles, proceed below
}
}
if (otType.isRole())
for (IMember m : ((IRoleType)otType).getMethodMappings(IRoleType.CALLINS))
result.add(m);
return result.toArray(new IMember[result.size()]);
} else {
StringTokenizer tokens = new StringTokenizer(suffix, "$"); //$NON-NLS-1$
ArrayList<IMember> mList = new ArrayList<IMember>();
switch (tokens.countTokens()) {
case 1: // role method guard
try {
selector = tokens.nextToken();
for (IMethod method : declaringClass.getMethods())
if (method.getElementName().equals(selector))
mList.add(method);
} catch (JavaModelException e) {
// failed to retrieve methods -> no result
}
break;
case 3: // binding guard (s.t. like "roleMeth$after$baseMeth")
String roleSelector = tokens.nextToken();
String modifier = tokens.nextToken();
String baseSelector = tokens.nextToken();
IRoleType roleType = (IRoleType) OTModelManager.getOTElement(declaringClass);
mappings:
for (IMethodMapping mapping : roleType.getMethodMappings(IRoleType.CALLINS)) {
if (mapping.getRoleMethod().getElementName().equals(roleSelector)) {
int callinKind = ((ICallinMapping)mapping).getCallinKind();
if (CallinMappingDeclaration.callinModifier(callinKind).equals(modifier))
{
try {
for (IMethod baseMethod : ((ICallinMapping)mapping).getBoundBaseMethods())
if (baseMethod.getElementName().equals(baseSelector)) {
mList.add(mapping);
continue mappings;
}
} catch (JavaModelException e) {
// failed to resolve base methods, proceed to next mapping
}
}
}
}
}
return mList.toArray(new IMember[mList.size()]);
}
}
boolean handleRoleInstantiations(IMember member, IProgressMonitor monitor) {
if (member.getElementType() == IJavaElement.METHOD) {
if (isLiftingCtor((IMethod)member)) {
return findRoleInstantiations(member.getDeclaringType(), monitor);
}
} else if (member.getElementType() == IJavaElement.TYPE) {
IType type = (IType) member;
if (OTModelManager.isRole(type)) {
return findRoleInstantiations(type, monitor);
}
}
return false;
}
private boolean isLiftingCtor(IMethod method) {
try {
if (!method.isConstructor()) return false;
String[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) return false;
IType declaringType = method.getDeclaringType();
IOTType otType = OTModelManager.getOTElement(declaringType);
if (otType == null || !otType.isRole()) return false;
String baseClassName = ((IRoleType) otType).getBaseclassName();
if (baseClassName == null) return false;
String paramType = Signature.toString(parameterTypes[0]);
if (baseClassName.indexOf('.') != -1)
return baseClassName.equals(paramType);
return baseClassName.equals(Signature.getSimpleName(paramType));
} catch (JavaModelException jme) {
return false;
}
}
boolean findRoleInstantiations(IType roleType, IProgressMonitor monitor) {
try {
initCalls();
Map<String, MethodCall> fElements = getFElements();
// search-engine based search for constructor invocations and lifting references:
Map<String, MethodCall> ctorElements = searchRoleCtorInvocations(roleType, monitor);
if (!ctorElements.isEmpty()) {
for (MethodCall call: ctorElements.values())
addCallToCache(call);
fElements.putAll(ctorElements);
}
// direct lookup of all non-static callins in this role:
IRoleType otRoleType = (IRoleType) OTModelManager.getOTElement(roleType);
for (IMethodMapping mapping : otRoleType.getMethodMappings(IRoleType.CALLINS)) {
if (!Flags.isStatic(mapping.getRoleMethod().getFlags())) {
MethodCall call = new MethodCall(mapping);
addCallToCache(call);
fElements.put(mapping.getHandleIdentifier(), call);
}
}
return true;
} catch (CoreException ex) {
return false; // search failed or some found element doesn't exist?
}
}
Map<String, MethodCall> searchRoleCtorInvocations(IType roleType, IProgressMonitor monitor) throws CoreException {
// mimicked after pieces from CallerMethodWrapper.findChildren():
SearchPattern pattern= SearchPattern.createPattern(roleType,
IJavaSearchConstants.CLASS_INSTANCE_CREATION_TYPE_REFERENCE, // this is lifting-aware!
SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
SearchEngine searchEngine= new SearchEngine();
MappingReferenceSearchRequestor searchRequestor= new MappingReferenceSearchRequestor();
IJavaSearchScope defaultSearchScope= getSearchScope();
boolean isWorkspaceScope= SearchEngine.createWorkspaceScope().equals(defaultSearchScope);
IJavaSearchScope searchScope= isWorkspaceScope ? getAccurateSearchScope(defaultSearchScope, roleType) : defaultSearchScope;
searchEngine.search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }, searchScope, searchRequestor,
monitor);
return searchRequestor.getCallers();
}
// while searching for children in a caller-hierarchy, constrain all
// method-mapping matches to the non-declaration side.
callin void findChildren() {
findingCallers.set(new Object());
try {
base.findChildren();
} finally {
findingCallers.remove();
}
}
findChildren <- replace findChildren;
}
/** Handle for an invisible class. */
protected class CallSearchResultCollector playedBy CallSearchResultCollector
{
protected void addMember(IMember member, IMember calledMember, int start, int end) -> void addMember(IMember member, IMember calledMember, int start, int end);
}
}