package ru.naumen.gintonic.guice.analyzer; import java.util.List; import java.util.Set; import org.eclipse.jdt.core.dom.*; import ru.naumen.gintonic.GinTonicPlugin; import ru.naumen.gintonic.guice.GuiceConstants; import ru.naumen.gintonic.guice.annotations.IGuiceAnnotation; import ru.naumen.gintonic.guice.statements.*; import ru.naumen.gintonic.project.source.references.SourceCodeReference; import ru.naumen.gintonic.utils.*; /** * @author tmajunke */ @SuppressWarnings("unchecked") public final class GuiceAnalyzerAstVisitor extends ASTVisitor { private ITypeBinding guiceModuleTypeBinding; private List<BindingDefinition> bindingStatements = ListUtils.newArrayListWithCapacity(30); private List<InstallModuleStatement> installModuleStatements = ListUtils.newArrayListWithCapacity(50); private BindingDefinition bindingStatement; private IGuiceAnnotation guiceAnnotation; private String boundType; private String implType; private String scopeType; private boolean isEagerSingleton; public List<BindingDefinition> getBindingStatements() { return bindingStatements; } public ITypeBinding getGuiceModuleTypeBinding() { return guiceModuleTypeBinding; } public List<InstallModuleStatement> getInstallModuleStatements() { return installModuleStatements; } @Override public boolean visit(TypeDeclaration node) { if (guiceModuleTypeBinding == null) { guiceModuleTypeBinding = node.resolveBinding(); } Preconditions.checkNotNull(guiceModuleTypeBinding); return true; } private void addBinding(BindingDefinition bindingStatement) { bindingStatements.add(bindingStatement); } private void addModuleInstallStatement(InstallModuleStatement installModuleStatement) { installModuleStatements.add(installModuleStatement); } @Override public boolean visit(MethodInvocation methodInvocation) { SimpleName name = methodInvocation.getName(); String methodname = name.getIdentifier(); IMethodBinding methodBinding = methodInvocation.resolveMethodBinding(); if (methodBinding == null) { /* Maybe error! */ return false; } ITypeBinding declaringClass = methodBinding.getDeclaringClass(); ITypeBinding declaringClassTypeDecl = declaringClass.getTypeDeclaration(); /* com.google.inject.binder.LinkedBindingBuilder */ String declType = declaringClassTypeDecl.getQualifiedName(); List<Expression> arguments = methodInvocation.arguments(); /* The most method calls only expect one parameter, so we store it here */ Expression firstArgument = null; if (arguments.size() > 0) { firstArgument = arguments.get(0); } if (GuiceConstants.ABSTRACT_MODULES.contains(declType)) { if (methodname.equals("bind")) { boundType = ExpressionUtils.getQualifiedTypeName(firstArgument); if (bindingStatement == null) { /* e.g bind(X.class).in(Scopes.SINGLETON); */ bindingStatement = new BindingDefinition(); } /* * as bind is the last methodInvocation in the method chain, we * can now initialize the binding statement with the types we * recorded (e.g scopeType). */ finishBindingStatement(methodInvocation); } else if (methodname.equals("install")) { String installType = ExpressionUtils.getQualifiedTypeName(firstArgument); if (GuiceConstants.INJECT_MODULES.contains(installType)) { // Binding factory installation InstallBindingStatement statement = new InstallBindingStatement(); injectSourceCodeReference(methodInvocation, statement); if (!(firstArgument instanceof MethodInvocation)) { // maybe some kind of: install(variable); return true; } MethodInvocation factoryMethod = (MethodInvocation) firstArgument; if (factoryMethod.arguments().size() > 0) { statement.setBoundType(GuiceConstants.SINGLETON_SCOPE); // .build(FactoryImpl.class) Object factory = factoryMethod.arguments().get(0); Type factoryType = getTypeOfArgument(factory); if(factoryType == null && factory instanceof SimpleName) { IVariableBinding variable = ASTNodeUtils.getVariableBinding((SimpleName)factory); statement.setBoundType(variable.getType().getQualifiedName()); } else if(factoryType != null) { statement.setBoundType(factoryType.resolveBinding().getQualifiedName()); } else { // Can't define factory type. Maybe method and binding code too complicate? return true; } // .implement(Type.class, Impl.class) Expression expression = factoryMethod.getExpression(); while(expression instanceof MethodInvocation) { MethodInvocation implExpression = (MethodInvocation) expression; if(implExpression.arguments().size() == 2) { Object sourceArg = implExpression.arguments().get(0); Type sourceType = getTypeOfArgument(sourceArg); Object implArg = implExpression.arguments().get(1); Type implType = getTypeOfArgument(implArg); if (sourceType == null || implType == null) { // Can't define source and impl type. Maybe method and binding code too complicate? if(expression == implExpression.getExpression()) { throw new RuntimeException("Unexpected infinite loop exception"); } expression = implExpression.getExpression(); continue; } statement.addImpl(sourceType.resolveBinding().getQualifiedName(), implType.resolveBinding().getQualifiedName()); } expression = implExpression.getExpression(); } bindingStatement = statement; addBinding(bindingStatement); clearScope(); } } else { // Module installation InstallModuleStatement installModuleStatement = new InstallModuleStatement(); installModuleStatement.setModuleNameFullyQualified(installType); injectSourceCodeReference(methodInvocation, installModuleStatement); addModuleInstallStatement(installModuleStatement); clearScope(); } } else if (methodname.equals("bindConstant")) { finishBindingStatement(methodInvocation); } else if (methodname.equals("binder")) { /* We don't care */ } else { unsupportedMethod(GuiceConstants.ABSTRACT_MODULE, methodname); } } else if (GuiceConstants.LINKED_BINDING_BUILDERS.contains(declType)) { /* * Special case, as the LinkedBindingBuilder is also used from the * MapBinder and Setbinder. But we don't want these bindings! */ MethodInvocation firstMethodInvocation = MethodInvocationUtils .resolveFirstMethodInvocation(methodInvocation); SimpleName firstMethodName = firstMethodInvocation.getName(); String firstMethodNameAsString = firstMethodName.toString(); boolean isMultibinderInChain = firstMethodNameAsString.equals("addBinding"); if (!isMultibinderInChain) { if (methodname.equals("to")) { bindingStatement = new LinkedBindingStatement(); implType = ExpressionUtils.getQualifiedTypeName(firstArgument); } else if (methodname.equals("toInstance")) { bindingStatement = new InstanceBindingStatement(); implType = ExpressionUtils.getQualifiedTypeName(firstArgument); } else if (methodname.equals("toProvider")) { ProviderBindingStatement providerBindingStatement = new ProviderBindingStatement(); String providerClassType = ExpressionUtils.getQualifiedTypeName(firstArgument); providerBindingStatement.setProviderClassType(providerClassType); bindingStatement = providerBindingStatement; } else { unsupportedMethod(GuiceConstants.LINKED_BINDING_BUILDER, methodname); } } } else if (GuiceConstants.ANNOTATED_BINDING_BUILDERS.contains(declType)) { /* void in(Scope scope); */ /* void in(Class<? extends Annotation> scopeAnnotation); */ /* void asEagerSingleton(); */ if (methodname.equals("annotatedWith")) { resolveAnnotations(firstArgument); } else { unsupportedMethod(GuiceConstants.ANNOTATED_BINDING_BUILDER, methodname); } } else if (GuiceConstants.CONSTANT_BINDING_BUILDERS.contains(declType)) { if (methodname.equals("to")) { bindingStatement = new ConstantBindingStatement(); implType = ExpressionUtils.getQualifiedTypeName(firstArgument); /* * Special case in constants as impl and interface type are * equal */ boundType = implType; } /* By the way - no scopes for constants! */ else { unsupportedMethod(GuiceConstants.CONSTANT_BINDING_BUILDER, methodname); } } else if (GuiceConstants.ANNOTATED_CONSTANT_BINDING_BUILDERS.contains(declType)) { if (methodname.equals("annotatedWith")) { resolveAnnotations(firstArgument); } else { unsupportedMethod(GuiceConstants.ANNOTATED_CONSTANT_BINDING_BUILDER, methodname); } } else if (GuiceConstants.SCOPED_BINDING_BUILDERS.contains(declType)) { if (methodname.equals("in")) { if (firstArgument instanceof QualifiedName) { QualifiedName qualifiedName = (QualifiedName) firstArgument; String fullyQualifiedName = qualifiedName.getFullyQualifiedName(); if (fullyQualifiedName.equals("Scopes.SINGLETON")) { scopeType = GuiceConstants.SINGLETON_SCOPE; } } } else if (methodname.equals("asEagerSingleton")) { isEagerSingleton = true; scopeType = GuiceConstants.SINGLETON_SCOPE; } else { unsupportedMethod(GuiceConstants.ANNOTATED_CONSTANT_BINDING_BUILDER, methodname); } } else if (GuiceConstants.MAP_BINDERS.contains(declType)) { /* */ if (methodname.equals("addBinding")) { /* * Nothing to do, but don't delete it as otherwise it will be * logged as missing! */ } else if (methodname.equals("newMapBinder")) { inspectNewMapBinderInvocation(methodInvocation); } else { unsupportedMethod(GuiceConstants.MAP_BINDER, methodname); } } else if (GuiceConstants.SET_BINDERS.contains(declType)) { /* */ if (methodname.equals("addBinding")) { /* * Nothing to do, but don't delete it as otherwise it will be * logged as missing! */ } else if (methodname.equals("newSetBinder")) { inspectNewSetBinderInvocation(methodInvocation); } else { unsupportedMethod(GuiceConstants.SET_BINDER, methodname); } } else { } return true; } public static Type getTypeOfArgument(Object argument) { Type resultType = null; if (argument instanceof ClassInstanceCreation) { resultType = ((ClassInstanceCreation)argument).getType(); if(resultType instanceof ParameterizedType && "TypeLiteral".equals(((ParameterizedType)resultType).getType().toString())) { resultType = (Type) ((ParameterizedType)resultType).typeArguments().get(0); } } else if (argument instanceof TypeLiteral) { resultType = ((TypeLiteral) argument).getType(); } return resultType; } private void injectSourceCodeReference(ASTNode astNode, GuiceStatement guiceStatement) { SourceCodeReference sourceCodeReference = new SourceCodeReference(); sourceCodeReference.setOffset(astNode.getStartPosition()); sourceCodeReference.setLength(astNode.getLength()); guiceStatement.setSourceCodeReference(sourceCodeReference); } private void resolveAnnotations(Expression firstArgument) { guiceAnnotation = ExpressionUtils.resolveGuiceAnnotation(firstArgument); } private void finishBindingStatement(MethodInvocation methodInvocation) { bindingStatement.setBoundType(boundType); if (implType != null) { ((LinkedBindingStatement) bindingStatement).setImplType(implType); } bindingStatement.setScopeType(scopeType); bindingStatement.setGuiceAnnotation(guiceAnnotation); bindingStatement.setEagerSingleton(isEagerSingleton); injectSourceCodeReference(methodInvocation, bindingStatement); addBinding(bindingStatement); clearScope(); } private void inspectNewSetBinderInvocation(MethodInvocation methodInvocation) { List<Expression> arguments = methodInvocation.arguments(); int size = arguments.size(); if (size == 3) { /* 3rd argument is the annotation. */ Expression expression = arguments.get(2); guiceAnnotation = ExpressionUtils.resolveGuiceAnnotation(expression); bindingStatement.setGuiceAnnotation(guiceAnnotation); } } private void inspectNewMapBinderInvocation(MethodInvocation methodInvocation) { List<Expression> arguments = methodInvocation.arguments(); if (arguments.size() == 4) { /* 3rd argument is the annotation. */ Expression expression = arguments.get(3); guiceAnnotation = ExpressionUtils.resolveGuiceAnnotation(expression); bindingStatement.setGuiceAnnotation(guiceAnnotation); } } private Set<String> logMessages = SetUtils.newHashSet(); private void unsupportedMethod(String type, String methodname) { String methodID = type + "#" + methodname; boolean contains = logMessages.contains(methodID); if (!contains) { GinTonicPlugin.logInfo(type + "#" + methodname + " not supported"); logMessages.add(methodID); } } @Override public boolean visit(VariableDeclarationStatement node) { Type type = node.getType(); if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; boolean isMapBinderType = TypeUtils.isMapBinderType(parameterizedType); if (isMapBinderType) { bindingStatement = new MapBinderCreateStatement(); String interfaceType = TypeUtils.wrapInType(parameterizedType.typeArguments(), StringUtils.MAP_TYPE); bindingStatement.setBoundType(interfaceType); injectSourceCodeReference(node, bindingStatement); addBinding(bindingStatement); return true; } boolean isSetBinderType = TypeUtils.isSetBinderType(parameterizedType); if (isSetBinderType) { bindingStatement = new SetBinderCreateStatement(); String boundType = TypeUtils.wrapInType(parameterizedType.typeArguments(), StringUtils.SET_TYPE); bindingStatement.setBoundType(boundType); injectSourceCodeReference(node, bindingStatement); addBinding(bindingStatement); return true; } } return true; } /* * Here we check the methods in the guice modules if they are provider * methods. */ @Override public boolean visit(MethodDeclaration node) { List<ASTNode> modifiers = node.modifiers(); AnnotationList markerAnnotationList = ASTNodeUtils.getAnnotationList(modifiers); if (markerAnnotationList.containsProvidesAnnotation()) { ProviderMethod providerMethod = new ProviderMethod(); Type returnType2 = node.getReturnType2(); String boundType = TypeUtils.resolveQualifiedName(returnType2); providerMethod.setBoundType(boundType); injectSourceCodeReference(node, providerMethod); guiceAnnotation = markerAnnotationList.getGuiceAnnotation(); providerMethod.setGuiceAnnotation(guiceAnnotation); if (markerAnnotationList.containsSingletonScopeAnnotation()) { providerMethod.setScopeType(GuiceConstants.SINGLETON_SCOPE); } addBinding(providerMethod); } return true; } private void clearScope() { boundType = null; implType = null; guiceAnnotation = null; scopeType = null; isEagerSingleton = false; bindingStatement = null; } }