/**
* Copyright (C) 2005 - 2012 Eric Van Dewoestine
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.eclim.plugin.cdt.command.hierarchy;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import org.eclim.annotation.Command;
import org.eclim.command.CommandLine;
import org.eclim.command.Options;
import org.eclim.plugin.cdt.command.search.SearchCommand;
import org.eclim.plugin.cdt.util.CUtils;
import org.eclim.util.StringUtils;
import org.eclim.util.file.Position;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.index.IIndexBinding;
import org.eclipse.cdt.core.index.IIndexManager;
import org.eclipse.cdt.core.index.IIndexName;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IFunction;
import org.eclipse.cdt.core.model.IFunctionDeclaration;
import org.eclipse.cdt.core.model.IProblemRequestor;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.core.model.IWorkingCopy;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ClassTypeHelper;
import org.eclipse.cdt.internal.core.model.ASTCache;
import org.eclipse.cdt.internal.ui.callhierarchy.CallHierarchyUI;
import org.eclipse.cdt.internal.ui.editor.ASTProvider;
import org.eclipse.cdt.internal.ui.editor.WorkingCopyManager;
import org.eclipse.cdt.internal.ui.viewsupport.IndexUI;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.part.FileEditorInput;
/**
* Command to generate a call hierarchy for a method or function.
*
* @author Eric Van Dewoestine
*/
@Command(
name = "c_callhierarchy",
options =
"REQUIRED p project ARG," +
"REQUIRED f file ARG," +
"REQUIRED o offset ARG," +
"REQUIRED l length ARG," +
"REQUIRED e encoding ARG"
)
public class CallHierarchyCommand
extends SearchCommand
{
/**
* {@inheritDoc}
* @see org.eclim.command.Command#execute(CommandLine)
*/
public Object execute(CommandLine commandLine)
throws Exception
{
String projectName = commandLine.getValue(Options.PROJECT_OPTION);
String file = commandLine.getValue(Options.FILE_OPTION);
int offset = getOffset(commandLine);
int length = commandLine.getIntValue(Options.LENGTH_OPTION);
ICProject cproject = CUtils.getCProject(projectName);
CUIPlugin cuiPlugin = CUIPlugin.getDefault();
ITranslationUnit src = CUtils.getTranslationUnit(cproject, file);
src = src.getSharedWorkingCopy(null, (IProblemRequestor)null);
IEditorInput input = new FileEditorInput((IFile)src.getResource());
// hack... there has to be a better way
WorkingCopyManager manager = (WorkingCopyManager)
cuiPlugin.getWorkingCopyManager();
manager.connect(input);
manager.setWorkingCopy(input, (IWorkingCopy)src);
HashMap<String,Object> result = new HashMap<String,Object>();
try{
src.open(null);
// more hacks to got get around gui dependency
ASTProvider provider = ASTProvider.getASTProvider();
Field astCache = ASTProvider.class.getDeclaredField("fCache");
astCache.setAccessible(true);
((ASTCache)astCache.get(provider)).setActiveElement(src);
TextSelection selection = new TextSelection(offset, length);
Method findDefinitions = CallHierarchyUI.class.getDeclaredMethod(
"findDefinitions",
ICProject.class, IEditorInput.class, ITextSelection.class);
findDefinitions.setAccessible(true);
ICElement[] elements = (ICElement[])findDefinitions.invoke(
null, cproject, input, selection);
if (elements != null && elements.length > 0) {
ICElement callee = elements[0];
Set<ICElement> seen = new HashSet<ICElement>();
ICProject project = callee.getCProject();
ICProject[] scope = getScope(SCOPE_PROJECT, project);
IIndex index = CCorePlugin.getIndexManager().getIndex(
scope, IIndexManager.ADD_DEPENDENCIES | IIndexManager.ADD_DEPENDENT);
index.acquireReadLock();
try{
IIndexName name = IndexUI.elementToName(index, callee);
result = formatElement(index, callee, name, seen);
}finally{
index.releaseReadLock();
}
}
}finally{
manager.removeWorkingCopy(input);
manager.disconnect(input);
}
return result;
}
private ArrayList<HashMap<String,Object>> findCalledBy(
IIndex index, ICElement callee, Set<ICElement> seen)
throws Exception
{
ArrayList<HashMap<String,Object>> results =
new ArrayList<HashMap<String,Object>>();
ICProject project = callee.getCProject();
IIndexBinding calleeBinding = IndexUI.elementToBinding(index, callee);
if (calleeBinding != null) {
results.addAll(findCalledBy(index, calleeBinding, true, project, seen));
if (calleeBinding instanceof ICPPMethod) {
IBinding[] overriddenBindings =
ClassTypeHelper.findOverridden((ICPPMethod)calleeBinding);
for (IBinding overriddenBinding : overriddenBindings) {
results.addAll(findCalledBy(
index, overriddenBinding, false, project, seen));
}
}
}
return results;
}
private ArrayList<HashMap<String,Object>> findCalledBy(
IIndex index,
IBinding callee,
boolean includeOrdinaryCalls,
ICProject project,
Set<ICElement> seen)
throws Exception
{
IIndexName[] names = index.findNames(
callee, IIndex.FIND_REFERENCES | IIndex.SEARCH_ACROSS_LANGUAGE_BOUNDARIES);
ArrayList<Call> calls = new ArrayList<Call>(names.length);
for (IIndexName name : names) {
if (includeOrdinaryCalls || name.couldBePolymorphicMethodCall()) {
IIndexName caller = name.getEnclosingDefinition();
if (caller == null) {
continue;
}
ICElement element = IndexUI.getCElementForName(project, index, caller);
if (element == null) {
continue;
}
calls.add(new Call(name, element));
}
}
Collections.sort(calls);
ArrayList<HashMap<String,Object>> results =
new ArrayList<HashMap<String,Object>>();
for (Call call : calls) {
results.add(formatElement(index, call.element, call.name, seen));
}
return results;
}
private HashMap<String,Object> formatElement(
IIndex index, ICElement element, IIndexName name, Set<ICElement> seen)
throws Exception
{
HashMap<String,Object> result = new HashMap<String,Object>();
String[] types = null;
if (element instanceof IFunction){
types = ((IFunction)element).getParameterTypes();
}else if (element instanceof IFunctionDeclaration){
types = ((IFunctionDeclaration)element).getParameterTypes();
}
String message = element.getElementName() +
'(' + StringUtils.join(types, ", ") + ')';
result.put("name", message);
if (name != null){
IResource resource = element.getResource();
if (resource != null){
String file = element.getResource()
.getLocation().toOSString().replace('\\', '/');
result.put("position",
Position.fromOffset(file, null, name.getNodeOffset(), 0));
}
}
if (!seen.contains(element)){
seen.add(element);
result.put("calledBy", findCalledBy(index, element, seen));
}
return result;
}
private static class Call
implements Comparable<Call>
{
private static final Collator COLLATOR = Collator.getInstance();
public IIndexName name;
public ICElement element;
public String location;
public Call(IIndexName name, ICElement element)
{
this.name = name;
this.element = element;
this.location =
element.getResource().getLocation().toOSString() +
element.getElementName();
}
/**
* {@inheritDoc}
* @see Comparable#compareTo(T)
*/
public int compareTo(Call o)
{
int result = COLLATOR.compare(location, o.location);
if (result == 0){
return name.getNodeOffset() - o.name.getNodeOffset();
}
return result;
}
}
}