/** * Copyright (C) 2005 - 2014 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.IASTNode; 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.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.core.runtime.NullProgressMonitor; 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," + "OPTIONAL c callees NOARG" ) public class CallHierarchyCommand extends SearchCommand { private static final String CALLEES_OPTION = "c"; @Override public Object execute(CommandLine commandLine) throws Exception { String projectName = commandLine.getValue(Options.PROJECT_OPTION); String file = commandLine.getValue(Options.FILE_OPTION); boolean callees = commandLine.hasOption(CALLEES_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); CCorePlugin.getIndexManager().update( new ICElement[]{src}, IIndexManager.UPDATE_ALL); CCorePlugin.getIndexManager().joinIndexer(3000, new NullProgressMonitor()); src = src.getWorkingCopy(); 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{ // 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 element = elements[0]; Set<ICElement> seen = new HashSet<ICElement>(); ICProject project = element.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, element); result = formatElement(index, new Call(name, element), seen, callees); }finally{ index.releaseReadLock(); } } }finally{ manager.removeWorkingCopy(input); manager.disconnect(input); } return result; } private ArrayList<HashMap<String,Object>> findCallers( IIndex index, ICElement element, Set<ICElement> seen) throws Exception { ArrayList<HashMap<String,Object>> results = new ArrayList<HashMap<String,Object>>(); ICProject project = element.getCProject(); IIndexBinding calleeBinding = IndexUI.elementToBinding(index, element); if (calleeBinding != null) { results.addAll(findCallers(index, calleeBinding, true, project, seen)); if (calleeBinding instanceof ICPPMethod) { // cdt 8.1.1 requires a second arg (point: IASTNode), but cdt 8.1.1 // hasn't been released independent of eclipse 4.2.1, so distros are // less likely to have it. So rather than attempt to force an // eclipse/cdt version, we'll resort to reflection for now this time. /*IBinding[] overriddenBindings = ClassTypeHelper.findOverridden((ICPPMethod)calleeBinding, null);*/ IBinding[] overriddenBindings = null; try{ Method findOverridden = ClassTypeHelper.class .getMethod("findOverridden", ICPPMethod.class, IASTNode.class); overriddenBindings = (IBinding[]) findOverridden.invoke(null, (ICPPMethod)calleeBinding, null); }catch(NoSuchMethodException nsme){ Method findOverridden = ClassTypeHelper.class .getMethod("findOverridden", ICPPMethod.class); overriddenBindings = (IBinding[]) findOverridden.invoke(null, (ICPPMethod)calleeBinding); } for (IBinding overriddenBinding : overriddenBindings) { results.addAll(findCallers( index, overriddenBinding, false, project, seen)); } } } return results; } private ArrayList<HashMap<String,Object>> findCallers( IIndex index, IBinding binding, boolean includeOrdinaryCalls, ICProject project, Set<ICElement> seen) throws Exception { IIndexName[] names = index.findNames( binding, 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, seen, false)); } return results; } private ArrayList<HashMap<String,Object>> findCallees( IIndex index, ICElement element, Set<ICElement> seen) throws Exception { ICProject project = element.getCProject(); IIndexName name = IndexUI.elementToName(index, element); IIndexName[] enclosedNames = name.getEnclosedNames(); ArrayList<Call> calls = new ArrayList<Call>(enclosedNames.length); for (IIndexName enclosedName : enclosedNames) { IIndexBinding binding = index.findBinding(enclosedName); IIndexName[] enclosedDefinitionNames = index.findDefinitions(binding); if (enclosedDefinitionNames == null || enclosedDefinitionNames.length == 0){ continue; } IIndexName enclosedDefinitionName = enclosedDefinitionNames[0]; ICElement enclosedElement = IndexUI.getCElementForName(project, index, enclosedDefinitionName); if (enclosedElement == null) { continue; } if (enclosedElement instanceof IFunction) { calls.add(new Call( enclosedName, enclosedElement, element.getResource())); } } Collections.sort(calls); ArrayList<HashMap<String,Object>> results = new ArrayList<HashMap<String,Object>>(); for (Call call : calls) { results.add(formatElement(index, call, seen, true)); } return results; } private HashMap<String,Object> formatElement( IIndex index, Call call, Set<ICElement> seen, boolean callees) throws Exception { HashMap<String,Object> result = new HashMap<String,Object>(); String[] types = null; IIndexName name = call.name; ICElement element = call.element; 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 = call.resource; if (resource != null){ String file = resource.getLocation().toOSString().replace('\\', '/'); result.put("position", Position.fromOffset(file, null, name.getNodeOffset(), 0)); } } if (!seen.contains(element)){ seen.add(element); if (callees) { result.put("callees", findCallees(index, element, seen)); } else { result.put("callers", findCallers(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 IResource resource; public Call(IIndexName name, ICElement element) { this.name = name; this.element = element; this.resource = element.getResource(); } public Call(IIndexName name, ICElement element, IResource resource) { this(name, element); this.resource = resource; } @Override public int compareTo(Call o) { int result = 0; if (resource != null && o.resource != null){ String location1 = resource.getLocation().toOSString() + element.getElementName(); String location2 = resource.getLocation().toOSString() + element.getElementName(); result = COLLATOR.compare(location1, location2); }else if (resource != null && o.resource == null){ result = -1; }else if (resource == null && o.resource != null){ result = 1; } if (result == 0){ return name.getNodeOffset() - o.name.getNodeOffset(); } return result; } } }