/** * Copyright (c) 2010, 2011 Darmstadt University of Technology. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Marcel Bruch - initial API and implementation. */ package org.eclipse.recommenders.internal.rcp; import static com.google.common.base.Optional.*; import static org.eclipse.recommenders.internal.rcp.l10n.LogMessages.*; import static org.eclipse.recommenders.rcp.JavaElementSelectionEvent.JavaElementSelectionLocation.*; import static org.eclipse.recommenders.rcp.utils.JdtUtils.findTypeRoot; import static org.eclipse.recommenders.utils.Checks.ensureIsNotNull; import static org.eclipse.recommenders.utils.Logs.log; import java.util.HashMap; import java.util.Map; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.Initializer; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; import org.eclipse.jface.text.ITextSelection; import org.eclipse.recommenders.rcp.JavaElementSelectionEvent.JavaElementSelectionLocation; import org.eclipse.ui.IEditorPart; import com.google.common.base.Optional; /** * Utility class that resolves a selected java element from editor selection or structured selection. */ @SuppressWarnings("restriction") public final class JavaElementSelections { private JavaElementSelections() { // Not meant to be instantiated } @SuppressWarnings("serial") private static final Map<StructuralPropertyDescriptor, JavaElementSelectionLocation> MAPPING = new HashMap<StructuralPropertyDescriptor, JavaElementSelectionLocation>() { { put(CompilationUnit.IMPORTS_PROPERTY, TYPE_DECLARATION); put(CompilationUnit.PACKAGE_PROPERTY, TYPE_DECLARATION); put(CompilationUnit.TYPES_PROPERTY, TYPE_DECLARATION); put(TypeDeclaration.BODY_DECLARATIONS_PROPERTY, TYPE_DECLARATION); put(TypeDeclaration.INTERFACE_PROPERTY, TYPE_DECLARATION); put(TypeDeclaration.JAVADOC_PROPERTY, TYPE_DECLARATION); put(TypeDeclaration.MODIFIERS2_PROPERTY, TYPE_DECLARATION); put(TypeDeclaration.NAME_PROPERTY, TYPE_DECLARATION); put(TypeDeclaration.SUPERCLASS_TYPE_PROPERTY, TYPE_DECLARATION_EXTENDS); put(TypeDeclaration.SUPER_INTERFACE_TYPES_PROPERTY, TYPE_DECLARATION_IMPLEMENTS); put(TypeDeclaration.TYPE_PARAMETERS_PROPERTY, UNKNOWN); put(MethodDeclaration.BODY_PROPERTY, METHOD_BODY); put(MethodDeclaration.CONSTRUCTOR_PROPERTY, METHOD_DECLARATION); put(MethodDeclaration.JAVADOC_PROPERTY, METHOD_DECLARATION); put(MethodDeclaration.MODIFIERS2_PROPERTY, METHOD_DECLARATION); put(MethodDeclaration.NAME_PROPERTY, METHOD_DECLARATION); put(MethodDeclaration.PARAMETERS_PROPERTY, METHOD_DECLARATION_PARAMETER); put(MethodDeclaration.RETURN_TYPE2_PROPERTY, METHOD_DECLARATION_RETURN); put(MethodDeclaration.THROWN_EXCEPTIONS_PROPERTY, METHOD_DECLARATION_THROWS); put(MethodDeclaration.TYPE_PARAMETERS_PROPERTY, UNKNOWN); put(Initializer.BODY_PROPERTY, METHOD_BODY); put(Initializer.MODIFIERS2_PROPERTY, METHOD_DECLARATION); put(FieldDeclaration.FRAGMENTS_PROPERTY, FIELD_DECLARATION_INITIALIZER); put(VariableDeclarationFragment.NAME_PROPERTY, FIELD_DECLARATION); put(FieldDeclaration.TYPE_PROPERTY, FIELD_DECLARATION); put(FieldDeclaration.JAVADOC_PROPERTY, FIELD_DECLARATION); put(FieldDeclaration.MODIFIERS2_PROPERTY, FIELD_DECLARATION); } }; /** * Returns the {@link IJavaElement} at the current offset or {@link Optional#absent()} if resolving fails. */ public static Optional<IJavaElement> resolveJavaElementFromEditor(final IEditorPart editor, final ITextSelection selection) { ensureIsNotNull(editor); ensureIsNotNull(selection); if (!isValidSelection(selection)) { return absent(); } if (editor instanceof JavaEditor) { final JavaEditor javaEditor = (JavaEditor) editor; return resolveJavaElementFromEditor(javaEditor, selection.getOffset()); } return absent(); } private static boolean isValidSelection(final ITextSelection selection) { return selection.getOffset() != -1; } /** * Returns the {@link IJavaElement} at the given offset in the editor. * */ public static Optional<IJavaElement> resolveJavaElementFromEditor(final JavaEditor editor, final int offset) { ensureIsNotNull(editor); ITypeRoot root = findTypeRoot(editor).orNull(); if (root != null && root.exists()) { return resolveJavaElementFromTypeRootInEditor(root, offset); } return absent(); } /** * Returns the {@link IJavaElement} at the given offset. If no {@link IJavaElement} is selected, the innermost * enclosing {@link IJavaElement} is returned (e.g., the declaring method or type). If both selection resolutions * fail, {@link Optional#absent()} is returned. */ public static Optional<IJavaElement> resolveJavaElementFromTypeRootInEditor(final ITypeRoot root, final int offset) { ensureIsNotNull(root); try { if (isInvalidSelection(root, offset)) { return absent(); } // try resolve elements at current offset final IJavaElement[] elements = root.codeSelect(offset, 0); if (elements.length > 0) { // return java element under cursor/selection start return of(elements[0]); } else { // XXX MB: decided against selection changes because these // frequent changes were too disturbing return absent(); // ignore that for a while: // // if no java element has been selected, return the innermost // Java element enclosing a given offset. // // This might evaluate to null. // IJavaElement enclosingElement = root.getElementAt(offset); // if (enclosingElement == null) { // // selection occurred in empty space somewhere before the // type declaration. // // return type-root then. // enclosingElement = root; // } // return of(enclosingElement); } } catch (final Exception e) { // actually, these can happen when using snipmatch's in-editor completion. // fractions of seconds seem potentially to lead to this exception, thus, we swallow them here. if (!isInvalidSelection(root, offset)) { log(ERROR_FAILED_TO_RESOLVE_SELECTION, root.getHandleIdentifier(), offset, e); } return absent(); } } private static boolean isInvalidSelection(ITypeRoot root, final int offset) { try { if (!root.exists()) { return true; } // check whether the type root is part of an package fragment root. If not, it's an invalid selection and // all resolutions are likely to fail. Thus, return true (=invalid): IJavaElement ancestor = root.getAncestor(IJavaProject.PACKAGE_FRAGMENT_ROOT); if (!ancestor.exists()) { return true; } ISourceRange range = root.getSourceRange(); return range == null || offset < 0 || offset > range.getLength(); } catch (Exception e) { log(ERROR_EXCEPTION_WHILE_CHECKING_OFFSETS, e); return false; } } public static JavaElementSelectionLocation resolveSelectionLocationFromAst(final CompilationUnit astRoot, final int offset) { ensureIsNotNull(astRoot); final ASTNode selectedNode = NodeFinder.perform(astRoot, offset, 0); if (selectedNode == null) { // this *should* never happen but it *can* happen... return JavaElementSelectionLocation.UNKNOWN; } return resolveSelectionLocationFromAstNode(selectedNode); } public static JavaElementSelectionLocation resolveSelectionLocationFromAstNode(final ASTNode node) { if (node == null) { return JavaElementSelectionLocation.UNKNOWN; } // handle a direct selection on a declaration node, i.e., the users // select a whitespace as in // "public $ void do(){}": // TODO Review: create second(?) mapping switch (node.getNodeType()) { case ASTNode.COMPILATION_UNIT: case ASTNode.TYPE_DECLARATION: return TYPE_DECLARATION; case ASTNode.METHOD_DECLARATION: case ASTNode.INITIALIZER: return METHOD_DECLARATION; case ASTNode.FIELD_DECLARATION: return FIELD_DECLARATION; default: } return resolveSelectionLocationFromNonMemberDeclarationNode(node); } /** * some inner node that is not a method, a type or a field declaration node... */ private static JavaElementSelectionLocation resolveSelectionLocationFromNonMemberDeclarationNode(ASTNode node) { // deal with special case that no parent exists: for instance, if empty // spaces before the package declaration // are selected, we translate this to type declaration: ASTNode parent = node.getParent(); if (parent == null) { return JavaElementSelectionLocation.TYPE_DECLARATION; } // we have a child node selected. Let's figure out which location this // translates best: while (node != null) { final StructuralPropertyDescriptor locationInParent = node.getLocationInParent(); switch (parent.getNodeType()) { case ASTNode.VARIABLE_DECLARATION_FRAGMENT: if (isVariableNameSelectionInFieldDeclaration(parent, locationInParent)) { return FIELD_DECLARATION; } break; case ASTNode.COMPILATION_UNIT: case ASTNode.TYPE_DECLARATION: case ASTNode.METHOD_DECLARATION: case ASTNode.FIELD_DECLARATION: case ASTNode.INITIALIZER: return mapLocationInParent(locationInParent); default: break; } node = parent; parent = parent.getParent(); } return JavaElementSelectionLocation.UNKNOWN; } private static boolean isVariableNameSelectionInFieldDeclaration(final ASTNode parent, final StructuralPropertyDescriptor locationInParent) { final ASTNode superparent = parent.getParent(); return superparent instanceof FieldDeclaration && VariableDeclarationFragment.NAME_PROPERTY == locationInParent; } private static JavaElementSelectionLocation mapLocationInParent( final StructuralPropertyDescriptor locationInParent) { final JavaElementSelectionLocation res = MAPPING.get(locationInParent); return res != null ? res : JavaElementSelectionLocation.UNKNOWN; } // TODO Review: rename method public static JavaElementSelectionLocation resolveSelectionLocationFromJavaElement(final IJavaElement element) { ensureIsNotNull(element); switch (element.getElementType()) { case IJavaElement.CLASS_FILE: case IJavaElement.COMPILATION_UNIT: case IJavaElement.PACKAGE_DECLARATION: case IJavaElement.IMPORT_DECLARATION: case IJavaElement.IMPORT_CONTAINER: case IJavaElement.TYPE: return TYPE_DECLARATION; case IJavaElement.METHOD: case IJavaElement.INITIALIZER: return METHOD_DECLARATION; case IJavaElement.FIELD: return FIELD_DECLARATION; case IJavaElement.LOCAL_VARIABLE: // shouldn't happen in a viewer selection, right? return METHOD_BODY; case IJavaElement.JAVA_MODEL: case IJavaElement.PACKAGE_FRAGMENT: case IJavaElement.PACKAGE_FRAGMENT_ROOT: case IJavaElement.ANNOTATION: default: return JavaElementSelectionLocation.UNKNOWN; } } }