org.eclim.plugin.jdt.command.search.SearchCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.eclim.plugin.jdt.command.search.SearchCommand.java

Source

/**
 * 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.search;

import java.io.File;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemManager;
import org.apache.commons.vfs.VFS;

import org.apache.tools.ant.taskdefs.condition.Os;

import org.eclim.Services;

import org.eclim.annotation.Command;

import org.eclim.command.CommandLine;
import org.eclim.command.Options;

import org.eclim.plugin.core.command.AbstractCommand;

import org.eclim.plugin.core.util.ProjectUtils;

import org.eclim.plugin.jdt.util.JavaUtils;

import org.eclim.util.StringUtils;

import org.eclim.util.file.FileUtils;
import org.eclim.util.file.Position;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;

import org.eclipse.jdt.core.ICodeAssist;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;

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;

/**
 * Command to handle java search requests.
 *
 * @author Eric Van Dewoestine
 */
@Command(name = "java_search", options = "OPTIONAL n project ARG," + "OPTIONAL f file ARG,"
        + "OPTIONAL o offset ARG," + "OPTIONAL e encoding ARG," + "OPTIONAL l length ARG,"
        + "OPTIONAL p pattern ARG," + "OPTIONAL t type ARG," + "OPTIONAL x context ARG," + "OPTIONAL s scope ARG,"
        + "OPTIONAL i case_insensitive NOARG")
public class SearchCommand extends AbstractCommand {
    public static final String CONTEXT_ALL = "all";
    public static final String CONTEXT_DECLARATIONS = "declarations";
    public static final String CONTEXT_IMPLEMENTORS = "implementors";
    public static final String CONTEXT_REFERENCES = "references";

    public static final String SCOPE_ALL = "all";
    public static final String SCOPE_PROJECT = "project";
    public static final String SCOPE_TYPE = "type";

    public static final String TYPE_ALL = "all";
    public static final String TYPE_ANNOTATION = "annotation";
    public static final String TYPE_CLASS = "class";
    public static final String TYPE_CLASS_OR_ENUM = "classOrEnum";
    public static final String TYPE_CLASS_OR_INTERFACE = "classOrInterface";
    public static final String TYPE_CONSTRUCTOR = "constructor";
    public static final String TYPE_ENUM = "enum";
    public static final String TYPE_FIELD = "field";
    public static final String TYPE_INTERFACE = "interface";
    public static final String TYPE_METHOD = "method";
    public static final String TYPE_PACKAGE = "package";
    public static final String TYPE_TYPE = "type";

    private static final Pattern INNER_CLASS = Pattern.compile("(.*?)(\\w+\\$)(\\w.*)");

    protected static final String ANDROID_NATURE = "com.android.ide.eclipse.adt.AndroidNature";
    private static final Pattern ANDROID_JDK_URL = Pattern.compile(".*android\\.jar!java.*");

    /**
     * Key used for results that don't belong to any user specified sort key.
     */
    private static final String DEFAULT_SORT_KEY = StringUtils.EMPTY;

    @Override
    public Object execute(CommandLine commandLine) throws Exception {
        List<SearchMatch> matches = executeSearch(commandLine);
        String projectName = commandLine.getValue(Options.NAME_OPTION);
        IProject project = projectName != null ? ProjectUtils.getProject(projectName) : null;

        String[] sortKeys = getSortKeys(project);

        // Store the results keyed by the sort key
        Map<String, List<Position>> positionMap = new HashMap<String, List<Position>>();

        for (SearchMatch match : matches) {
            IJavaElement element = (IJavaElement) match.getElement();
            if (element != null) {
                int elementType = element.getElementType();
                if (elementType != IJavaElement.PACKAGE_FRAGMENT
                        && elementType != IJavaElement.PACKAGE_FRAGMENT_ROOT) {
                    Position result = createPosition(project, match);
                    if (result != null) {
                        String sortKey = getSortKey(result, sortKeys);
                        List<Position> positions = positionMap.get(sortKey);
                        if (positions == null) {
                            positions = new ArrayList<Position>();
                            positionMap.put(sortKey, positions);
                        }

                        positions.add(result);
                    }
                }
            }
        }

        // Assemble the final results in the sorted order
        List<Position> results = null;
        for (String sortKey : sortKeys) {
            List<Position> positions = positionMap.get(sortKey);
            if (positions == null) {
                continue;
            }

            if (results == null) {
                results = positions;
            } else {
                results.addAll(positions);
            }
        }

        return results == null ? Collections.emptyList() : results;
    }

    /**
     * Returns the sort keys for this project. It will include the default
     * key <code>DEFAULT_SORT_KEY</code>.
     *
     * @param project The current project.
     */
    private String[] getSortKeys(IProject project) throws Exception {

        String[] sortSettings = getPreferences().getArrayValue(project, "org.eclim.java.search.sort");

        for (int i = 0; i < sortSettings.length; i++) {
            String sortKey = sortSettings[i];
            sortKey = sortKey.replace('\\', '/');
            if (!sortKey.endsWith("/")) {
                sortKey += '/';
            }
            sortSettings[i] = sortKey;
        }

        String[] sortKeys = Arrays.copyOf(sortSettings, sortSettings.length + 1);
        sortKeys[sortKeys.length - 1] = DEFAULT_SORT_KEY;
        return sortKeys;
    }

    /**
     * Returns the sort key associated with the given <code>Position</code>.
     */
    private String getSortKey(Position position, String[] sortKeys) throws Exception {
        String path = ProjectUtils.getProjectRelativePath(position.getFilename());
        if (path != null) {
            for (String sortKey : sortKeys) {
                if (path.startsWith(sortKey)) {
                    return sortKey;
                }
            }
        }

        return DEFAULT_SORT_KEY;
    }

    /**
     * Executes the search.
     *
     * @param commandLine The command line for the search.
     * @return The search results.
     */
    public List<SearchMatch> executeSearch(CommandLine commandLine) throws Exception {
        int context = -1;
        if (commandLine.hasOption(Options.CONTEXT_OPTION)) {
            context = getContext(commandLine.getValue(Options.CONTEXT_OPTION));
        }
        String project = commandLine.getValue(Options.NAME_OPTION);
        String scope = commandLine.getValue(Options.SCOPE_OPTION);
        String file = commandLine.getValue(Options.FILE_OPTION);
        String offset = commandLine.getValue(Options.OFFSET_OPTION);
        String length = commandLine.getValue(Options.LENGTH_OPTION);
        String pat = commandLine.getValue(Options.PATTERN_OPTION);

        SearchPattern pattern = null;
        IJavaProject javaProject = project != null ? JavaUtils.getJavaProject(project) : null;

        SearchRequestor requestor = new SearchRequestor();

        // element search
        if (file != null && offset != null && length != null) {
            int charOffset = getOffset(commandLine);
            IJavaElement element = getElement(javaProject, file, charOffset, Integer.parseInt(length));
            if (element != null) {
                // user requested a contextual search.
                if (context == -1) {
                    context = getElementContextualContext(element);

                    // jdt search doesn't support implementors for method searches, so
                    // switch to declarations.
                } else if (context == IJavaSearchConstants.IMPLEMENTORS
                        && element.getElementType() == IJavaElement.METHOD) {
                    context = IJavaSearchConstants.DECLARATIONS;
                    requestor = new ImplementorsSearchRequestor();
                }
                pattern = SearchPattern.createPattern(element, context);
            }

            // pattern search
        } else if (pat != null) {
            if (context == -1) {
                context = IJavaSearchConstants.DECLARATIONS;
            }

            int matchType = SearchPattern.R_EXACT_MATCH;

            // wild card character supplied, use pattern matching.
            if (pat.indexOf('*') != -1 || pat.indexOf('?') != -1) {
                matchType = SearchPattern.R_PATTERN_MATCH;

                // all upper case, add camel case support.
            } else if (pat.equals(pat.toUpperCase())) {
                matchType |= SearchPattern.R_CAMELCASE_MATCH;
            }

            boolean caseSensitive = !commandLine.hasOption(Options.CASE_INSENSITIVE_OPTION);
            if (caseSensitive) {
                matchType |= SearchPattern.R_CASE_SENSITIVE;
            }

            int type = getType(commandLine.getValue(Options.TYPE_OPTION));

            // jdt search doesn't support implementors for method searches, so switch
            // to declarations.
            if (type == IJavaSearchConstants.METHOD && context == IJavaSearchConstants.IMPLEMENTORS) {
                context = IJavaSearchConstants.DECLARATIONS;
                requestor = new ImplementorsSearchRequestor();
            }

            // hack for inner classes
            Matcher matcher = INNER_CLASS.matcher(pat);
            if (matcher.matches()) {
                // pattern search doesn't support org.test.Type$Inner or
                // org.test.Type.Inner, so convert it to org.test.*Inner, then filter
                // the results.
                pattern = SearchPattern.createPattern(matcher.replaceFirst("$1*$3"), type, context, matchType);
                Pattern toMatch = Pattern.compile(pat.replace(".", "\\.").replace("$", "\\$").replace("(", "\\(")
                        .replace(")", "\\)").replace("*", ".*").replace("?", "."));
                List<SearchMatch> matches = search(pattern, getScope(scope, javaProject));
                Iterator<SearchMatch> iterator = matches.iterator();
                while (iterator.hasNext()) {
                    SearchMatch match = iterator.next();
                    String name = JavaUtils.getFullyQualifiedName((IJavaElement) match.getElement()).replace("#",
                            ".");
                    if (!toMatch.matcher(name).matches()) {
                        iterator.remove();
                    }
                }
                return matches;
            }

            pattern = SearchPattern.createPattern(pat, type, context, matchType);

            // bad search request
        } else {
            throw new IllegalArgumentException(Services.getMessage("java_search.indeterminate"));
        }

        List<SearchMatch> matches = search(pattern, getScope(scope, javaProject), requestor);
        return matches;
    }

    /**
     * Executes the search.
     *
     * @param pattern The search pattern.
     * @param scope The scope of the search (file, project, all, etc).
     *
     * @return List of matches.
     */
    protected List<SearchMatch> search(SearchPattern pattern, IJavaSearchScope scope) throws CoreException {
        return search(pattern, scope, new SearchRequestor());
    }

    /**
     * Executes the search.
     *
     * @param pattern The search pattern.
     * @param scope The scope of the search (file, project, all, etc).
     * @param requestor The search requestor used to accept matches.
     *
     * @return List of matches.
     */
    protected List<SearchMatch> search(SearchPattern pattern, IJavaSearchScope scope, SearchRequestor requestor)
            throws CoreException {
        if (pattern != null) {
            SearchEngine engine = new SearchEngine();
            SearchParticipant[] participants = new SearchParticipant[] {
                    SearchEngine.getDefaultSearchParticipant() };
            engine.search(pattern, participants, scope, requestor, null);
        }
        return requestor.getMatches();
    }

    /**
     * Gets a IJavaElement by its position.
     *
     * @param javaProject The IJavaProject the file is in.
     * @param filename The file containing the element.
     * @param offset The offset of the element in the file.
     * @param length The lenght of the element.
     * @return The element.
     */
    protected IJavaElement getElement(IJavaProject javaProject, String filename, int offset, int length)
            throws Exception {
        ICodeAssist code = null;
        try {
            code = JavaUtils.getCompilationUnit(javaProject, filename);
        } catch (IllegalArgumentException iae) {
            // source not found, try location the class file.
            code = JavaUtils.findClassFile(javaProject, filename);
        }

        if (code != null) {
            IJavaElement[] elements = code.codeSelect(offset, length);
            if (elements != null && elements.length > 0) {
                return elements[0];
            }
        }
        return null;
    }

    /**
     * Determines if the supplied path is a jar compatible path that can be
     * converted to a jar: url.
     *
     * @param path The IPath.
     * @return True if a jar or zip, false otherwise.
     */
    protected boolean isJarArchive(IPath path) {
        String ext = path.getFileExtension();
        return ext != null && ext.toLowerCase().matches("^(jar|zip)$");
    }

    /**
     * Creates a Position from the supplied SearchMatch.
     *
     * @param project The project searching from.
     * @param match The SearchMatch.
     * @return The Position.
     */
    protected Position createPosition(IProject project, SearchMatch match) throws Exception {
        IJavaElement element = (IJavaElement) match.getElement();
        IJavaElement parent = JavaUtils.getPrimaryElement(element);

        String file = null;
        String elementName = JavaUtils.getFullyQualifiedName(parent);
        if (parent.getElementType() == IJavaElement.CLASS_FILE) {
            IResource resource = parent.getResource();
            // occurs with a referenced project as a lib with no source and class
            // files that are not archived in that project
            if (resource != null && resource.getType() == IResource.FILE && !isJarArchive(resource.getLocation())) {
                file = resource.getLocation().toOSString();

            } else {
                IPath path = null;
                IPackageFragmentRoot root = (IPackageFragmentRoot) parent.getParent().getParent();
                resource = root.getResource();
                if (resource != null) {
                    if (resource.getType() == IResource.PROJECT) {
                        path = ProjectUtils.getIPath((IProject) resource);
                    } else {
                        path = resource.getLocation();
                    }
                } else {
                    path = root.getPath();
                }

                String classFile = elementName.replace('.', File.separatorChar);
                if (isJarArchive(path)) {
                    file = "jar:file://" + path.toOSString() + '!' + classFile + ".class";
                } else {
                    file = path.toOSString() + '/' + classFile + ".class";
                }

                // android injects its jdk classes, so filter those out if the project
                // doesn't have the android nature.
                if (ANDROID_JDK_URL.matcher(file).matches() && project != null
                        && !project.hasNature(ANDROID_NATURE)) {
                    return null;
                }

                // if a source path attachment exists, use it.
                IPath srcPath = root.getSourceAttachmentPath();
                if (srcPath != null) {
                    String rootPath;
                    IProject elementProject = root.getJavaProject().getProject();

                    // determine if src path is project relative or file system absolute.
                    if (srcPath.isAbsolute() && elementProject.getName().equals(srcPath.segment(0))) {
                        rootPath = ProjectUtils.getFilePath(elementProject, srcPath.toString());
                    } else {
                        rootPath = srcPath.toOSString();
                    }
                    String srcFile = FileUtils.toUrl(rootPath + File.separator + classFile + ".java");

                    // see if source file exists at source path.
                    FileSystemManager fsManager = VFS.getManager();
                    FileObject fileObject = fsManager.resolveFile(srcFile.replace("%", "%25"));
                    if (fileObject.exists()) {
                        file = srcFile;

                        // jdk sources on osx are under a "src/" dir in the jar
                    } else if (Os.isFamily(Os.FAMILY_MAC)) {
                        srcFile = FileUtils
                                .toUrl(rootPath + File.separator + "src" + File.separator + classFile + ".java");
                        fileObject = fsManager.resolveFile(srcFile.replace("%", "%25"));
                        if (fileObject.exists()) {
                            file = srcFile;
                        }
                    }
                }
            }
        } else {
            IPath location = match.getResource().getLocation();
            file = location != null ? location.toOSString() : null;
        }

        elementName = JavaUtils.getFullyQualifiedName(element);
        return Position.fromOffset(file.replace('\\', '/'), elementName, match.getOffset(), match.getLength());
    }

    /**
     * Gets the search scope to use.
     *
     * @param scope The string name of the scope.
     * @param project The current project.
     *
     * @return The IJavaSearchScope equivalent.
     */
    protected IJavaSearchScope getScope(String scope, IJavaProject project) throws Exception {
        if (project == null) {
            return SearchEngine.createWorkspaceScope();
        } else if (SCOPE_PROJECT.equals(scope)) {
            return SearchEngine.createJavaSearchScope(new IJavaElement[] { project });
        }
        return SearchEngine.createWorkspaceScope();
    }

    /**
     * Determines the appropriate context to used base on the elements context.
     *
     * @param element The IJavaElement.
     * @return The int context
     */
    protected int getElementContextualContext(IJavaElement element) {
        Class<?> theClass = element.getClass();

        // type / field / method declaration
        if (theClass.equals(org.eclipse.jdt.internal.core.SourceType.class)
                || theClass.equals(org.eclipse.jdt.internal.core.SourceField.class)
                || theClass.equals(org.eclipse.jdt.internal.core.SourceMethod.class)) {
            return IJavaSearchConstants.REFERENCES;
        }

        return IJavaSearchConstants.DECLARATIONS;
    }

    /**
     * Translates the string context to the int equivalent.
     *
     * @param context The String context.
     * @return The int context
     */
    protected int getContext(String context) {
        if (CONTEXT_ALL.equals(context)) {
            return IJavaSearchConstants.ALL_OCCURRENCES;
        } else if (CONTEXT_IMPLEMENTORS.equals(context)) {
            return IJavaSearchConstants.IMPLEMENTORS;
        } else if (CONTEXT_REFERENCES.equals(context)) {
            return IJavaSearchConstants.REFERENCES;
        }
        return IJavaSearchConstants.DECLARATIONS;
    }

    /**
     * Translates the string type to the int equivalent.
     *
     * @param type The String type.
     * @return The int type.
     */
    protected int getType(String type) {
        if (TYPE_ANNOTATION.equals(type)) {
            return IJavaSearchConstants.ANNOTATION_TYPE;
        } else if (TYPE_CLASS.equals(type)) {
            return IJavaSearchConstants.CLASS;
        } else if (TYPE_CLASS_OR_ENUM.equals(type)) {
            return IJavaSearchConstants.CLASS_AND_ENUM;
        } else if (TYPE_CLASS_OR_INTERFACE.equals(type)) {
            return IJavaSearchConstants.CLASS_AND_INTERFACE;
        } else if (TYPE_CONSTRUCTOR.equals(type)) {
            return IJavaSearchConstants.CONSTRUCTOR;
        } else if (TYPE_ENUM.equals(type)) {
            return IJavaSearchConstants.ENUM;
        } else if (TYPE_FIELD.equals(type)) {
            return IJavaSearchConstants.FIELD;
        } else if (TYPE_INTERFACE.equals(type)) {
            return IJavaSearchConstants.INTERFACE;
        } else if (TYPE_METHOD.equals(type)) {
            return IJavaSearchConstants.METHOD;
        } else if (TYPE_PACKAGE.equals(type)) {
            return IJavaSearchConstants.PACKAGE;
        }
        return IJavaSearchConstants.TYPE;
    }
}