/** * 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.jdt.command.hierarchy; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import org.eclim.annotation.Command; import org.eclim.command.CommandLine; import org.eclim.command.Options; import org.eclim.plugin.jdt.command.search.SearchCommand; import org.eclim.plugin.jdt.util.JavaUtils; import org.eclim.util.file.Position; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.search.SearchEngine; 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.ui.viewsupport.AppearanceAwareLabelProvider; import org.eclipse.jdt.ui.JavaElementLabels; /** * Command to generate a call hierarchy for a method. * * @author Alexandre Fonseca */ @Command( name = "java_callhierarchy", options = "REQUIRED p project ARG," + "REQUIRED f file ARG," + "REQUIRED o offset ARG," + "REQUIRED l length ARG," + "REQUIRED e encoding ARG," + "OPTIONAL s scope ARG," + "OPTIONAL c callees NOARG" ) public class CallHierarchyCommand extends SearchCommand { private static final String CALLEES_OPTION = "c"; private static final int MAX_CALL_DEPTH = 3; @Override public Object execute(CommandLine commandLine) throws Exception { HashMap<String,Object> result = new HashMap<String, Object>(); String project = commandLine.getValue(Options.PROJECT_OPTION); String file = commandLine.getValue(Options.FILE_OPTION); boolean callees = commandLine.hasOption(CALLEES_OPTION); String scope = commandLine.getValue(Options.SCOPE_OPTION); int length = commandLine.getIntValue(Options.LENGTH_OPTION); int offset = getOffset(commandLine); ICompilationUnit src = JavaUtils.getCompilationUnit(project, file); IJavaElement[] elements = src.codeSelect(offset, length); if(elements == null || elements.length == 0){ return result; } IJavaElement element = elements[0]; if (element instanceof IMethod) { IMethod method = (IMethod) element; IMember[] members = new IMember[]{method}; MethodWrapper[] roots; CallHierarchy callHierarchy = CallHierarchy.getDefault(); callHierarchy.setSearchScope(getScope(scope, src.getJavaProject())); Comparator<MethodWrapper> comparator = null; if (callees) { roots = callHierarchy.getCalleeRoots(members); } else { roots = callHierarchy.getCallerRoots(members); // Following Eclipse's GUI, callers are ordered // alphabetically, callees by position in function. comparator = new Comparator<MethodWrapper>() { public int compare(MethodWrapper o1, MethodWrapper o2) { return o1.getName().compareToIgnoreCase(o2.getName()); } }; } if (roots.length > 0) { // Is it possible to have multiple roots? If so we'll need // to change this. result = formatRoot(roots[0], comparator, callees); IResource resource = method.getResource(); ISourceRange sourceRange = method.getSourceRange(); // The root element doesn't get his location like all the others // (this happens with the GUI too). So add it ourselves. result.put("position", Position.fromOffset( resource.getLocation().toOSString(), null, sourceRange.getOffset(), sourceRange.getLength())); } } return result; } private ArrayList<HashMap<String,Object>> formatRoots( MethodWrapper[] roots, Comparator<MethodWrapper> comparator, boolean callees) throws Exception { ArrayList<HashMap<String,Object>> results = new ArrayList<HashMap<String,Object>>(); if (comparator != null) { Arrays.sort(roots, comparator); } for (MethodWrapper root : roots) { if (root.getLevel() > MAX_CALL_DEPTH || root.isRecursive()) { continue; } results.add(formatRoot(root, comparator, callees)); } return results; } private HashMap<String, Object> formatRoot( MethodWrapper root, Comparator<MethodWrapper> comparator, boolean callees) throws Exception { IMember member = root.getMember(); String memberName = JavaElementLabels.getTextLabel(member, AppearanceAwareLabelProvider.DEFAULT_TEXTFLAGS | JavaElementLabels.ALL_POST_QUALIFIED | JavaElementLabels.P_COMPRESSED); CallLocation location = root.getMethodCall().getFirstCallLocation(); HashMap<String,Object> result = new HashMap<String, Object>(); result.put("name", memberName); if (location != null) { // If caller, locationMember == member. If callee, locationMember // is the function where the callee is called. IMember locationMember = location.getMember(); IResource resource = locationMember.getResource(); if (resource != null) { String file = resource.getLocation().toOSString().replace('\\', '/'); int offset = location.getStart(); result.put("position", Position.fromOffset( file, null, offset, location.getEnd() - offset)); } } result.put(callees ? "callees" : "callers", formatRoots( root.getCalls(new NullProgressMonitor()), comparator, callees)); return result; } }