package com.anjlab.eclipse.tapestry5.internal.visitors; import org.apache.commons.lang3.StringUtils; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.TypeLiteral; import org.eclipse.jdt.core.search.IJavaSearchConstants; import com.anjlab.eclipse.tapestry5.Activator; import com.anjlab.eclipse.tapestry5.DeclarationReference.ASTNodeReference; import com.anjlab.eclipse.tapestry5.EclipseUtils; import com.anjlab.eclipse.tapestry5.ObjectCallback; import com.anjlab.eclipse.tapestry5.TapestryModule; import com.anjlab.eclipse.tapestry5.TapestryService; import com.anjlab.eclipse.tapestry5.TapestryService.ServiceDefinition; import com.anjlab.eclipse.tapestry5.TapestryUtils; public class TapestryServiceCapturingVisitor extends ASTVisitor { private final IProgressMonitor monitor; private final TapestryModule tapestryModule; private final ObjectCallback<TapestryService, RuntimeException> serviceFound; private ServiceDefinition serviceDefinition; public TapestryServiceCapturingVisitor(IProgressMonitor monitor, TapestryModule tapestryModule, ObjectCallback<TapestryService, RuntimeException> serviceFound) { this.monitor = monitor; this.tapestryModule = tapestryModule; this.serviceFound = serviceFound; } private ServiceDefinition serviceDefinition() { if (serviceDefinition == null) { serviceDefinition = new ServiceDefinition(); } return serviceDefinition; } private boolean analyzeInvocationChain(MethodInvocation node) { if (node.getExpression() instanceof MethodInvocation) { visit((MethodInvocation) node.getExpression()); } else { // Unsupported method chain, drop captured service definition serviceDefinition = null; } return false; } @Override public boolean visit(MethodInvocation node) { if (monitor.isCanceled()) { return false; } String identifier = node.getName().getIdentifier(); if ("withMarker".equals(identifier)) { // Copy annotations from module class serviceDefinition().addMarkers(tapestryModule.markers()); for (Object arg : node.arguments()) { if (arg instanceof TypeLiteral) { serviceDefinition().addMarker( EclipseUtils.toClassName(tapestryModule.getEclipseProject(), (TypeLiteral) arg)); } } return analyzeInvocationChain(node); } else if ("preventReloading".equals(identifier)) { serviceDefinition().setPreventReloading(true); return analyzeInvocationChain(node); } else if ("preventDecoration".equals(identifier)) { serviceDefinition().setPreventDecoration(true); return analyzeInvocationChain(node); } else if ("eagerLoad".equals(identifier)) { serviceDefinition().setEagerLoad(true); return analyzeInvocationChain(node); } else if ("scope".equals(identifier)) { if (node.arguments().size() == 1) { serviceDefinition().setScope( EclipseUtils.evalExpression( tapestryModule.getEclipseProject(), node.arguments().get(0))); } return analyzeInvocationChain(node); } else if ("withId".equals(identifier)) { if (node.arguments().size() == 1) { serviceDefinition().setId( EclipseUtils.evalExpression( tapestryModule.getEclipseProject(), node.arguments().get(0))); } return analyzeInvocationChain(node); } else if ("withSimpleId".equals(identifier)) { serviceDefinition().setSimpleId(true); return analyzeInvocationChain(node); } else if ("bind".equals(identifier)) { bind(node); serviceDefinition = null; return false; } return super.visit(node); } private void bind(MethodInvocation node) { String intfClass = null; String implClass = null; switch (node.arguments().size()) { case 2: // Interface, Implementation intfClass = typeLiteralToClassName(node.arguments().get(0)); implClass = typeLiteralToClassName(node.arguments().get(1)); break; case 1: // Check if it's actually an interface, or a non-interface class // It it's an interface, then name of implementation class can be computed String className = typeLiteralToClassName(node.arguments().get(0)); // TODO Implement caching for type lookups IType type = EclipseUtils.findTypeDeclaration( tapestryModule.getEclipseProject(), IJavaSearchConstants.CLASS_AND_INTERFACE, className); if (type == null) { // Something is wrong with this service binding Activator.getDefault().logError("Unable to find java type: " + className); return; } try { if (type.isInterface()) { intfClass = type.getFullyQualifiedName(); implClass = intfClass + "Impl"; } else { implClass = type.getFullyQualifiedName(); } break; } catch (JavaModelException e) { Activator.getDefault().logError("Unable to read java model", e); return; } default: Activator.getDefault().logWarning("Unexpected method signature: " + node); return; } ServiceDefinition definition = serviceDefinition(); definition.setIntfClass(intfClass); definition.setImplClass(implClass); if (definition.isSimpleId()) { if (StringUtils.isNotEmpty(definition.getImplClass())) { definition.setId(TapestryUtils.getSimpleName(definition.getImplClass())); } } else if (StringUtils.isEmpty(definition.getId())) { // Try getting serviceId from @ServiceId & @Named annotations on implementation class // see tapestry's ServiceBinderImpl#bind() for details String serviceId = readServiceIdFromAnnotations(definition.getImplClass()); String classNameForServiceId = StringUtils.defaultIfEmpty( // In tapestry it's not possible to have null interface, // it's plugin's implementation detail -- // should fallback to implClass in this case definition.getIntfClass(), definition.getImplClass()); definition.setId( StringUtils.defaultIfEmpty( serviceId, StringUtils.isNotEmpty(classNameForServiceId) ? TapestryUtils.getSimpleName(classNameForServiceId) : null)); } if (StringUtils.isEmpty(definition.getId())) { // Something is wrong with this service definition // Maybe that was not ServiceBinder#bind(), but some other `bind` method? return; } definition.resolveMarkers(tapestryModule); serviceFound.callback(new TapestryService( tapestryModule, definition, new ASTNodeReference(tapestryModule, tapestryModule.getModuleClass(), node))); } private String readServiceIdFromAnnotations(String implClass) { try { IType implType = EclipseUtils.findTypeDeclaration( tapestryModule.getEclipseProject(), IJavaSearchConstants.CLASS, implClass); if (implType == null) { return null; } IAnnotation serviceIdAnnotation = TapestryUtils.findAnnotation( implType.getAnnotations(), TapestryUtils.ORG_APACHE_TAPESTRY5_IOC_ANNOTATIONS_SERVICE_ID); if (serviceIdAnnotation != null) { return EclipseUtils.readFirstValueFromAnnotation( tapestryModule.getEclipseProject(), serviceIdAnnotation, "value"); } IAnnotation namedAnnotation = TapestryUtils.findAnnotation( implType.getAnnotations(), TapestryUtils.JAVAX_INJECT_NAMED); if (namedAnnotation != null) { return StringUtils.trimToNull( EclipseUtils.readFirstValueFromAnnotation( tapestryModule.getEclipseProject(), namedAnnotation, "value")); } } catch (JavaModelException e) { Activator.getDefault().logError("Error determining ServiceId from implementation class", e); } return null; } private String typeLiteralToClassName(Object node) { if (node instanceof TypeLiteral) { return EclipseUtils.toClassName(tapestryModule.getEclipseProject(), (TypeLiteral) node); } return null; } }