package ru.naumen.gintonic.guice.injection; import static ru.naumen.gintonic.guice.GuiceConstants.SIMPLE_INJECT; import java.util.ArrayList; import java.util.List; import org.eclipse.jdt.core.*; import org.eclipse.jdt.core.dom.*; import org.eclipse.jdt.ui.JavaUI; import org.eclipse.jdt.ui.SharedASTProvider; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.texteditor.ITextEditor; import ru.naumen.gintonic.guice.GuiceConstants; import ru.naumen.gintonic.guice.annotations.IGuiceAnnotation; import ru.naumen.gintonic.utils.*; /** * Methods for finding {@link IInjectionPoint}s. * * @author tmajunke */ public class InjectionPointDao { /** * Returns all {@link InjectionPoint}s for the given compilation unit. The * superclasses of the given compilation unit are also included in the * returned list. Returns an empty list if no {@link InjectionPoint}s could * be found. */ public List<InjectionPoint> findAllByICompilationUnit( ICompilationUnit compilationUnit) throws JavaModelException { final List<InjectionPoint> injectionPoints = new ArrayList<InjectionPoint>( 30); List<IType> types = new ArrayList<IType>(5); IType primaryType = compilationUnit.findPrimaryType(); types.add(primaryType); ITypeHierarchy supertypeHierarchy = primaryType.newSupertypeHierarchy(null); IType[] allSuperClasses = supertypeHierarchy.getAllSuperclasses(primaryType); for (IType iType : allSuperClasses) { String typeQualified = iType.getFullyQualifiedName(); if (typeQualified.equals("java.lang.Object")) { continue; } types.add(iType); } for (IType iType : types) { ITypeRoot typeRootOfType = iType.getTypeRoot(); findInjectionPoints(typeRootOfType, injectionPoints); } return injectionPoints; } private void findInjectionPoints(ITypeRoot typeRoot, final List<InjectionPoint> injectionPoints) { final CompilationUnit compilationUnit = SharedASTProvider.getAST( typeRoot, SharedASTProvider.WAIT_YES, null); compilationUnit.accept(new ASTVisitor(false) { private String identifier; @Override public boolean visit(FieldDeclaration fieldDeclaration) { /* We can skip static fields as they cannot be injected. */ boolean isStatic = FieldDeclarationUtils.isStatic(fieldDeclaration); if (isStatic) { return false; } fieldDeclaration.accept(new ASTVisitor() { @Override public boolean visit(VariableDeclarationFragment node) { SimpleName name = node.getName(); identifier = name.getIdentifier(); return false; } }); InjectionPoint injectionPoint = analyzeForInjectionPoint( fieldDeclaration, compilationUnit, identifier); if (injectionPoint != null) { injectionPoints.add(injectionPoint); } identifier = null; return false; } }); } /** * Returns the currently selected {@link IInjectionPoint} or null. */ public IInjectionPoint findCurrentlySelectedInjectionPoint() { IEditorPart editorPart = EclipseUtils.getActiveEditor(); if (editorPart == null | !(editorPart instanceof ITextEditor)) { return null; } final ITextEditor textEditor = (ITextEditor) editorPart; IEditorInput editorInput = textEditor.getEditorInput(); ITypeRoot editorInputTypeRoot = JavaUI.getEditorInputTypeRoot(editorInput); if (!(editorInputTypeRoot instanceof ICompilationUnit)) { return null; } ICompilationUnit icompilationUnit = (ICompilationUnit) editorInputTypeRoot; ISelectionProvider selectionProvider = textEditor.getSelectionProvider(); ISelection sel = selectionProvider.getSelection(); if (!(sel instanceof ITextSelection)) { return null; } ITextSelection currentSelection = (ITextSelection) sel; return findInjectionPointByTextSelection( icompilationUnit, currentSelection); } /** * Returns the {@link IInjectionPoint} that the textSelection refers to or * null. */ public IInjectionPoint findInjectionPointByTextSelection( ICompilationUnit iCompilationUnit, ITextSelection textSelection) { IJavaElement selectedJavaElement; try { selectedJavaElement = iCompilationUnit.getElementAt(textSelection.getOffset()); } catch (JavaModelException e) { throw new RuntimeException(e); } int elementType = selectedJavaElement.getElementType(); /** * Here we can perform a quick check on the IJavaElement if the * currently selected element can be an injection point. Trying to avoid the parsing. * * <pre> * IJavaElement.FIELD * @Inject * private IPianoPlayer<Bar> jackThePianoPlayer; * * IJavaElement.METHOD * @Provides * private Customer provideCustomer(@Seed Long seed) { * * IJavaElement.METHOD * @Inject * public void setServableDrinks(Set<Drink> servableDrinks) { * this.servableDrinks = servableDrinks; * } * * <pre> */ if (!(elementType == IJavaElement.FIELD || elementType == IJavaElement.METHOD)) { return null; } CompilationUnit compilationUnit = SharedASTProvider.getAST( iCompilationUnit, SharedASTProvider.WAIT_YES, null); int length = textSelection.getLength(); int offset = textSelection.getOffset(); ASTNode coveredNode = NodeFinder.perform( compilationUnit, offset, length); IInjectionPoint injectionPoint = findByAstNode( coveredNode, compilationUnit); return injectionPoint; } /** * Returns the given {@link ASTNode} as a {@link IInjectionPoint} if the * {@link ASTNode} * * <ul> * * <li>is an identifier of a field declaration and the field declaration is * annoted with {@link GuiceConstants#ANNOTATION_INJECT}. In this case * the returned type is a {@link InjectionPoint}.</li> * <li>is a parameter of a method declaration and the method declaration is * annoted with {@link GuiceConstants#PROVIDES}. In this case the * returned type is a {@link ProviderMethod}.</li> * </ul> * * @param astNode the {@link ASTNode} which maybe is an * {@link IInjectionPoint} * @param compilationUnit the compilationUnit of the {@link ASTNode} * @return the {@link IInjectionPoint} or null. */ public IInjectionPoint findByAstNode(ASTNode astNode, CompilationUnit compilationUnit) { if (!(astNode instanceof Name)) { return null; } Name name = (Name) astNode; IInjectionPoint injectionPoint = getGuiceFieldDeclarationIfFieldDeclaration( name, compilationUnit); if (injectionPoint != null) { return injectionPoint; } injectionPoint = getProviderMethod(name); if (injectionPoint != null) { return injectionPoint; } injectionPoint = getMethodInjection(name, compilationUnit); return injectionPoint; } private IInjectionPoint getMethodInjection(Name name, CompilationUnit compilationUnit) { String variableName = name.getFullyQualifiedName(); ASTNode methodNode = name.getParent().getParent(); if(methodNode.getNodeType() == ASTNode.METHOD_DECLARATION) { MethodDeclaration method = (MethodDeclaration) methodNode; boolean injectedMethod = false; for(Object modifier : method.modifiers()) { if (modifier instanceof MarkerAnnotation) { if(SIMPLE_INJECT.equals(((MarkerAnnotation) modifier).getTypeName().getFullyQualifiedName())) { injectedMethod = true; break; } } } if(!injectedMethod) { return null; } SingleVariableDeclaration variableDeclaration = MethodDeclarationUtils.getVariableDeclarationsByName( method, variableName); if (variableDeclaration != null) { return injectionPointFromVariable(variableName, variableDeclaration); } } return null; } private InjectionPoint injectionPointFromVariable(String variableName, SingleVariableDeclaration variableDeclaration) { @SuppressWarnings("unchecked") AnnotationList annotationList = ASTNodeUtils.getAnnotationList(variableDeclaration.modifiers()); Type type = variableDeclaration.getType(); IGuiceAnnotation guiceAnnotation = annotationList.getGuiceAnnotation(); return new InjectionPoint( type.resolveBinding(), guiceAnnotation, variableName, null, InjectionIsAttachedTo.CONSTRUCTOR); } public InjectionPoint getGuiceFieldDeclarationIfFieldDeclaration( ASTNode astNode, CompilationUnit astRoot) { if (!(astNode instanceof Name)) { return null; } Name name = (Name) astNode; final String variableName = name.getFullyQualifiedName(); FieldDeclaration fieldDeclaration = ASTNodeUtils.getFieldDeclaration(name); if (fieldDeclaration != null) { InjectionPoint guiceFieldDeclaration = analyzeForInjectionPoint( fieldDeclaration, astRoot, variableName); return guiceFieldDeclaration; } return null; } private IInjectionPoint getProviderMethod(Name name) { ASTNode parentNode = name.getParent(); if (parentNode instanceof SingleVariableDeclaration) { SingleVariableDeclaration singleVariableDeclaration = (SingleVariableDeclaration) parentNode; boolean isProviderMethod = ASTNodeUtils.isProviderMethod(singleVariableDeclaration.getParent()); if (isProviderMethod) { @SuppressWarnings("unchecked") AnnotationList markerAnnotationList = getAnnotationList(singleVariableDeclaration.modifiers()); Type type = singleVariableDeclaration.getType(); IGuiceAnnotation guiceAnnotation = markerAnnotationList.getGuiceAnnotation(); return new ProviderMethod( type.resolveBinding(), guiceAnnotation, name.getFullyQualifiedName()); } } return null; } private AnnotationList getAnnotationList(List<ASTNode> modifiers) { List<Annotation> annotations = ListUtils.newArrayListWithCapacity(modifiers.size()); for (ASTNode modifier : modifiers) { if (modifier instanceof Annotation) { annotations.add((Annotation) modifier); } } return new AnnotationList(annotations); } @SuppressWarnings("unchecked") private InjectionPoint analyzeForInjectionPoint( FieldDeclaration fieldDeclaration, CompilationUnit compilationUnit, String fieldName) { List<ASTNode> modifiers = fieldDeclaration.modifiers(); AnnotationList annotationList = ASTNodeUtils.getAnnotationList(modifiers); if (annotationList.containsInjectType()) { Type type = fieldDeclaration.getType(); IGuiceAnnotation guiceAnnotation = annotationList.getGuiceAnnotation(); return new InjectionPoint( type.resolveBinding(), guiceAnnotation, fieldName, fieldDeclaration, InjectionIsAttachedTo.FIELD); } /* * Check the @Inject constructor if we can find a parameter with the * same name as the selected field. */ MethodDeclaration constructor = MethodDeclarationUtils.getConstructorAnnotatedWithInject(compilationUnit); if (constructor != null) { SingleVariableDeclaration variableDeclaration = MethodDeclarationUtils.getVariableDeclarationsByName( constructor, fieldName); if (variableDeclaration != null) { return injectionPointFromVariable(fieldName, variableDeclaration); } } /* * Check if a guicified setter method exists. */ String setterMethodName = "set" + StringUtils.capitalize(fieldName); MethodDeclaration setter = ASTNodeUtils.getMethodByNameExpectSingleMethod( compilationUnit, setterMethodName); if (setter != null) { annotationList = ASTNodeUtils.getAnnotationList(setter.modifiers()); if (annotationList.containsInjectType()) { Type type = fieldDeclaration.getType(); IGuiceAnnotation guiceAnnotation = annotationList.getGuiceAnnotation(); return new InjectionPoint( type.resolveBinding(), guiceAnnotation, fieldName, fieldDeclaration, InjectionIsAttachedTo.SETTER); } } return null; } }