package com.anjlab.eclipse.tapestry5; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.IAnnotatable; import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.IClassFile; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMemberValuePair; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.search.IJavaSearchConstants; import com.anjlab.eclipse.tapestry5.DeclarationReference.ASTNodeReference; import com.anjlab.eclipse.tapestry5.DeclarationReference.JavaElementReference; import com.anjlab.eclipse.tapestry5.TapestryService.Matcher; import com.anjlab.eclipse.tapestry5.TapestryService.ServiceDefinition; import com.anjlab.eclipse.tapestry5.TapestryService.ServiceInstrumenter; import com.anjlab.eclipse.tapestry5.internal.DeclarationCapturingScope.InjectedDeclaration; import com.anjlab.eclipse.tapestry5.internal.Orderable; import com.anjlab.eclipse.tapestry5.internal.visitors.JavaScriptStackCapturingVisitor; import com.anjlab.eclipse.tapestry5.internal.visitors.LibraryMappingCapturingVisitor; import com.anjlab.eclipse.tapestry5.internal.visitors.TapestryServiceCapturingVisitor; import com.anjlab.eclipse.tapestry5.internal.visitors.TapestryServiceConfigurationCapturingVisitor; import com.anjlab.eclipse.tapestry5.internal.visitors.TapestryServiceDiscovery; public abstract class TapestryModule implements Openable { private TapestryProject project; private IType moduleClass; private List<TapestryModuleReference> references = new ArrayList<TapestryModuleReference>(); private boolean sourceAvailable; private boolean appModule; private boolean tapestryCoreModule; public TapestryModule(TapestryProject project, IType moduleClass) { this.project = project; this.moduleClass = moduleClass; } public TapestryProject getProject() { return project; } public IProject getEclipseProject() { return project.getProject(); } public void addReference(TapestryModuleReference reference) { if (!references.contains(reference)) { references.add(reference); } } public List<TapestryModuleReference> references() { return Collections.unmodifiableList(references); } public IType getModuleClass() { return moduleClass; } public String getName() { return moduleClass.getElementName(); } public void initialize(IProgressMonitor monitor) { subTask(monitor, "markers"); findMarkers(monitor); subTask(monitor, "imported modules"); findSubModules(monitor); subTask(monitor, "library mappings"); findLibraryMappings(monitor); subTask(monitor, "components"); findComponents(monitor); subTask(monitor, "services"); findServices(monitor); subTask(monitor, "JavaScript stacks"); findJavaScriptStacks(monitor); } private void subTask(IProgressMonitor monitor, String name) { monitor.subTask("Analyzing " + moduleClass.getFullyQualifiedName() + " (" + name + ")..."); } private volatile List<String> markers; private synchronized void findMarkers(IProgressMonitor monitor) { if (markers != null) { return; } try { markers = readMarkerAnnotation(moduleClass); } catch (JavaModelException e) { Activator.getDefault().logError("Error getting markers for " + getName(), e); } } public List<String> readMarkerAnnotation(IAnnotatable annotatable) throws JavaModelException { List<String> markers = new ArrayList<String>(); IAnnotation annotation = TapestryUtils.findAnnotation(annotatable.getAnnotations(), TapestryUtils.ORG_APACHE_TAPESTRY5_IOC_ANNOTATIONS_MARKER); if (annotation != null) { String[] typeLiterals = EclipseUtils.readValuesFromAnnotation(getEclipseProject(), annotation, "value"); for (String typeLiteral : typeLiterals) { String typeName = EclipseUtils.resolveTypeName(moduleClass, typeLiteral); markers.add(typeName); } } return markers; } public List<String> markers() { if (markers == null) { findMarkers(new NullProgressMonitor()); } return markers; } private volatile List<TapestryModule> subModules; public List<TapestryModule> subModules() { if (subModules == null) { findSubModules(new NullProgressMonitor()); } return subModules; } private synchronized void findSubModules(IProgressMonitor monitor) { if (subModules != null) { return; } subModules = new ArrayList<TapestryModule>(); try { final IAnnotation annotation = findSubmoduleAnnotation(); if (annotation == null) { return; } for (IMemberValuePair pair : annotation.getMemberValuePairs()) { Object[] classes = pair.getValue().getClass().isArray() ? (Object[]) pair.getValue() : new Object[] { pair.getValue() }; for (Object className : classes) { if (monitor.isCanceled()) { return; } String typeName = EclipseUtils.toClassNameFromImports( getEclipseProject(), moduleClass, (String) className); IType subModuleClass = EclipseUtils.findTypeDeclaration( moduleClass.getJavaProject().getProject(), IJavaSearchConstants.CLASS, typeName); if (subModuleClass != null) { subModules.add( Activator.getDefault() .getTapestryModuleFactory() .createTapestryModule( project, subModuleClass, new ObjectCallback<TapestryModule, RuntimeException>() { @Override public void callback(TapestryModule obj) { obj.addReference(new TapestryModuleReference(new JavaElementReference(obj, annotation), false) { @Override public String getLabel() { String annotationName = TapestryUtils.getSimpleName(annotation.getElementName()); return "via @" + annotationName + " of " + getName(); } }); } })); } } } } catch (JavaModelException e) { Activator.getDefault().logError("Error getting submodules for " + getName(), e); } } private IAnnotation findSubmoduleAnnotation() throws JavaModelException { IAnnotation annotation = TapestryUtils.findAnnotation( moduleClass.getAnnotations(), TapestryUtils.ORG_APACHE_TAPESTRY5_IOC_ANNOTATIONS_SUB_MODULE); if (annotation == null) { annotation = TapestryUtils.findAnnotation( moduleClass.getAnnotations(), TapestryUtils.ORG_APACHE_TAPESTRY5_IOC_ANNOTATIONS_IMPORT_MODULE); } return annotation; } private List<JavaScriptStack> javaScriptStacks; public List<JavaScriptStack> javaScriptStacks() { if (javaScriptStacks == null) { findJavaScriptStacks(new NullProgressMonitor()); } return javaScriptStacks; } private synchronized void findJavaScriptStacks(final IProgressMonitor monitor) { if (javaScriptStacks != null) { return; } javaScriptStacks = new ArrayList<JavaScriptStack>(); final ASTVisitor javaScriptStackCapturingVisitor = new JavaScriptStackCapturingVisitor( monitor, this, new ObjectCallback<JavaScriptStack, RuntimeException>() { @Override public void callback(JavaScriptStack stack) { javaScriptStacks.add(stack); } }); // JavaScriptStackCapturingVisitor is a heavy handler, // we don't want to parse every method declaration with it // So only parse actual JavaScriptStackSource contributions final TapestryService javaScriptStackSource = new TapestryService( null, new ServiceDefinition() .setId("JavaScriptStackSource") .setIntfClass("org.apache.tapestry5.services.javascript.JavaScriptStackSource"), null); visitContributions(javaScriptStackSource, javaScriptStackCapturingVisitor); } /* default */ void visitContributions(final TapestryService targetService, final ASTVisitor visitor) { for (ServiceInstrumenter contributor : contributors()) { if (contributor.getServiceMatcher().matches(targetService)) { DeclarationReference contributionDeclaration = contributor.getReference(); if (contributionDeclaration instanceof ASTNodeReference) { ASTNode node = ((ASTNodeReference) contributionDeclaration).getNode(); // Find declaring method while (!(node instanceof MethodDeclaration) && node != null) { node = node.getParent(); } if (node != null) { node.accept(visitor); } } else if (contributionDeclaration instanceof JavaElementReference) { final IJavaElement element = ((JavaElementReference) contributionDeclaration).getElement(); if (element instanceof IMethod) { // Parse source code of method definition final CompilationUnit unit = getModuleClassCompilationUnit(); if (unit != null) { unit.accept(new ASTVisitor() { @Override public boolean visit(MethodDeclaration node) { if (node.getName().getIdentifier().equals(element.getElementName())) { node.accept(visitor); } return false; } }); } } } } } } private volatile List<LibraryMapping> libraryMappings; public List<LibraryMapping> libraryMappings() { if (libraryMappings == null) { findLibraryMappings(new NullProgressMonitor()); } return libraryMappings; } public static final String[] ADD_OVERRIDE = { "add", "override" }; public static final String[] ADD_OVERRIDE_INSTANCE = { "addInstance", "overrideInstance" }; public static final String[] OVERRIDES = { "override", "overrideInstance" }; public static final String[] CONFIGURATION_METHODS = { "add", "addInstance", "override", "overrideInstance" }; private synchronized void findLibraryMappings(final IProgressMonitor monitor) { if (libraryMappings != null) { return; } libraryMappings = new ArrayList<LibraryMapping>(); CompilationUnit compilationUnit = getModuleClassCompilationUnit(); if (compilationUnit == null) { return; } compilationUnit.accept(new LibraryMappingCapturingVisitor(monitor, this, new ObjectCallback<LibraryMapping, RuntimeException>() { @Override public void callback(LibraryMapping mapping) { libraryMappings.add(mapping); } })); } private volatile List<TapestryService> services; private volatile List<ServiceInstrumenter> decorators; private volatile List<ServiceInstrumenter> advisors; private volatile List<ServiceInstrumenter> contributors; public List<TapestryService> services() { if (services == null) { findServices(new NullProgressMonitor()); } return services; } public List<ServiceInstrumenter> decorators() { if (decorators == null) { findServices(new NullProgressMonitor()); } return decorators; } public List<ServiceInstrumenter> advisors() { if (advisors == null) { findServices(new NullProgressMonitor()); } return advisors; } public List<ServiceInstrumenter> contributors() { if (contributors == null) { findServices(new NullProgressMonitor()); } return contributors; } private synchronized void findServices(final IProgressMonitor monitor) { if (services != null) { return; } services = new ArrayList<TapestryService>(); decorators = new ArrayList<ServiceInstrumenter>(); advisors = new ArrayList<ServiceInstrumenter>(); contributors = new ArrayList<ServiceInstrumenter>(); new TapestryServiceDiscovery( monitor, this, createServiceFoundCallback(), createAdvisorFoundCallback(), createContributorFoundCallback(), createDecoratorFoundCallback()) .run(); visitBindInvocations(monitor); } private ObjectCallback<ServiceInstrumenter, RuntimeException> createDecoratorFoundCallback() { return new ObjectCallback<ServiceInstrumenter, RuntimeException>() { @Override public void callback(ServiceInstrumenter decorator) { decorators.add(decorator); } }; } private ObjectCallback<ServiceInstrumenter, RuntimeException> createContributorFoundCallback() { return new ObjectCallback<ServiceInstrumenter, RuntimeException>() { @Override public void callback(ServiceInstrumenter contributor) { contributors.add(contributor); } }; } private ObjectCallback<ServiceInstrumenter, RuntimeException> createAdvisorFoundCallback() { return new ObjectCallback<ServiceInstrumenter, RuntimeException>() { @Override public void callback(ServiceInstrumenter advisor) { advisors.add(advisor); } }; } private void visitBindInvocations(final IProgressMonitor monitor) { final CompilationUnit compilationUnit = getModuleClassCompilationUnit(); if (compilationUnit == null) { return; } compilationUnit.accept( new TapestryServiceCapturingVisitor( monitor, this, createServiceFoundCallback())); } private ObjectCallback<TapestryService, RuntimeException> createServiceFoundCallback() { return new ObjectCallback<TapestryService, RuntimeException>() { @Override public void callback(TapestryService service) { addService(service); } }; } public void addService(TapestryService service) { // XXX Only add if not yet exists? services.add(service); } private CompilationUnit compilationUnit; private CompilationUnit getModuleClassCompilationUnit() { if (compilationUnit != null) { return compilationUnit; } IClassFile classFile = moduleClass.getClassFile(); String source = null; try { source = classFile != null ? classFile.getSource() : moduleClass.getCompilationUnit().getSource(); } catch (JavaModelException e) { Activator.getDefault().logError("Error getting source file", e); } this.sourceAvailable = source != null; if (!sourceAvailable) { return null; } compilationUnit = (CompilationUnit) EclipseUtils.parse(source, ASTParser.K_COMPILATION_UNIT); return compilationUnit; } private List<TapestryContext> components; public List<TapestryContext> getComponents() { if (components == null) { findComponents(new NullProgressMonitor()); } return components; } private synchronized void findComponents(IProgressMonitor monitor) { if (components != null) { return; } components = new ArrayList<TapestryContext>(); if (intermediateComponents != null) { components.addAll(intermediateComponents); } ObjectCallback<Object, RuntimeException> componentClassFound = createComponentClassFoundHandler(); for (LibraryMapping mapping : libraryMappings()) { String componentsPackage = mapping.getRootPackage() + ".components"; if (monitor.isCanceled()) { return; } if (mapping.getPathPrefix().isEmpty() && isTapestryCoreModule()) { // This package is from the AppModule for (TapestryModule module : getProject().modules()) { if (module.isAppModule()) { // Components should go to AppModule by using its own componentClassFound handler module.enumJavaClassesRecursively(monitor, componentsPackage); break; } } } else { enumJavaClassesRecursively(monitor, componentsPackage, componentClassFound); } } } private void enumJavaClassesRecursively(IProgressMonitor monitor, String componentsPackage) { enumJavaClassesRecursively(monitor, componentsPackage, createComponentClassFoundHandler()); } private List<TapestryContext> intermediateComponents; private ObjectCallback<Object, RuntimeException> createComponentClassFoundHandler() { ObjectCallback<Object, RuntimeException> componentClassFound = new ObjectCallback<Object, RuntimeException>() { @Override public void callback(Object obj) { TapestryContext componentContext = null; if (obj instanceof IFile) { componentContext = Activator.getDefault() .getTapestryContextFactory() .createTapestryContext((IFile) obj); } else if (obj instanceof IClassFile) { IClassFile classFile = (IClassFile) obj; // Ignore inner classes if (!classFile.getElementName().contains("$")) { componentContext = Activator.getDefault() .getTapestryContextFactory() .createTapestryContext(classFile); } } if (componentContext != null) { if (components != null) { // XXX Make this structure synchronized? components.add(componentContext); } else { if (intermediateComponents == null) { intermediateComponents = new ArrayList<TapestryContext>(); } intermediateComponents.add(componentContext); } } } }; return componentClassFound; } protected abstract void enumJavaClassesRecursively( IProgressMonitor monitor, String rootPackage, ObjectCallback<Object, RuntimeException> callback); public abstract TapestryFile getModuleFile(); public abstract boolean isReadOnly(); public boolean isSourceAvailable() { return sourceAvailable; } public void setAppModule(boolean appModule) { this.appModule = appModule; } public boolean isAppModule() { return appModule; } public void setTapestryCoreModule(boolean tapestryCoreModule) { this.tapestryCoreModule = tapestryCoreModule; } public boolean isTapestryCoreModule() { return tapestryCoreModule; } public List<LibraryMapping> libraryMappings(String libraryPrefix) throws JavaModelException { List<LibraryMapping> result = new ArrayList<LibraryMapping>(); for (LibraryMapping mapping : libraryMappings()) { if (mapping.getPathPrefix().equals(libraryPrefix)) { result.add(mapping); } } return result; } public abstract TapestryFile findClasspathFileCaseInsensitive(String path); @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof TapestryModule)) { return false; } return this.moduleClass.equals(((TapestryModule) obj).moduleClass); } @Override public int hashCode() { return moduleClass.getFullyQualifiedName().hashCode(); } public String getComponentName(TapestryContext context) { String packageName = context.getPackageName(); String componentFullName = packageName + "." + context.getName(); String componentsPackage; for (LibraryMapping mapping : libraryMappings()) { if (!mapping.getPathPrefix().isEmpty() && packageName.startsWith(mapping.getRootPackage())) { componentsPackage = mapping.getRootPackage() + ".components"; String pathPrefix = mapping.getPathPrefix().equals("core") ? "" : mapping.getPathPrefix() + "."; return pathPrefix + getComponentName(componentFullName, componentsPackage); } } componentsPackage = TapestryUtils.getComponentsPackage(getEclipseProject()); return componentsPackage != null && packageName.startsWith(componentsPackage) ? getComponentName(componentFullName, componentsPackage) : componentFullName; } private String getComponentName(String componentFullName, String componentsPackage) { return componentFullName.substring((componentsPackage + ".").length()); } public boolean isConditional() { for (TapestryModuleReference reference : references()) { if (reference.isConditional()) { return true; } } return false; } private List<Orderable<TapestryService>> symbolProviders; /* default */ List<Orderable<TapestryService>> symbolProviders(IProgressMonitor monitor) { if (symbolProviders == null) { findSymbolProviders(monitor); } return symbolProviders; } private synchronized void findSymbolProviders(IProgressMonitor monitor) { if (symbolProviders != null) { return; } symbolProviders = new ArrayList<Orderable<TapestryService>>(); // Tapestry symbols are contributions to SymbolProviders. // First we need to grab all contributions to SymbolSource, // this way we will get all SymbolProviders. final TapestryService symbolSource = new TapestryService( null, new ServiceDefinition() .setId("SymbolSource") .setIntfClass("org.apache.tapestry5.ioc.services.SymbolSource"), null); visitContributions(symbolSource, new TapestryServiceConfigurationCapturingVisitor(monitor, this) { @Override protected void orderedConfigurationAddOverride(MethodInvocation node, String id, Object value, String[] constraints) { if (value instanceof IType) { ServiceDefinition definition = new ServiceDefinition() .setId("SymbolProvider") .setIntfClass("org.apache.tapestry5.ioc.services.SymbolProvider") .setImplClass(((IType) value).getFullyQualifiedName()); TapestryService symbolProvider = new TapestryService( TapestryModule.this, definition, new ASTNodeReference(TapestryModule.this, TapestryModule.this.getModuleClass(), node)); symbolProviders.add(new Orderable<TapestryService>(id, symbolProvider, constraints)); } else if (value instanceof InjectedDeclaration) { InjectedDeclaration injectedValue = (InjectedDeclaration) value; if (injectedValue.isServiceInjection()) { Matcher matcher = injectedValue.createMatcher(TapestryModule.this); TapestryService symbolProvider = getProject().findService(matcher); if (symbolProvider != null) { symbolProviders.add(new Orderable<TapestryService>(id, symbolProvider, constraints)); } } } } } .usesOrderedConfiguration()); } private Map<TapestryService, List<TapestrySymbol>> providersToSymbols; /* default */ List<TapestrySymbol> symbolsFrom(TapestryService symbolProvider, IProgressMonitor monitor) { if (providersToSymbols == null) { throw new IllegalStateException(); } List<TapestrySymbol> symbols = providersToSymbols.get(symbolProvider); return symbols == null ? null : new ArrayList<TapestrySymbol>(symbols); } /* default */ synchronized void buildProvidersToSymbolsMap(List<TapestryService> symbolProviders, IProgressMonitor monitor) { if (providersToSymbols != null) { return; } providersToSymbols = new HashMap<TapestryService, List<TapestrySymbol>>(); for (final TapestryService symbolProvider : symbolProviders) { visitContributions(symbolProvider, new TapestryServiceConfigurationCapturingVisitor(monitor, TapestryModule.this) { @Override protected void mappedConfigurationAddOverride(MethodInvocation node, Object key, Object value) { if (!(key instanceof String)) { return; } String symbolName = (String) key; List<TapestrySymbol> providerSymbols = providersToSymbols.get(symbolProvider); if (providerSymbols == null) { providerSymbols = new ArrayList<TapestrySymbol>(); providersToSymbols.put(symbolProvider, providerSymbols); } if (value instanceof String) { providerSymbols.add( new TapestrySymbol( symbolName, (String) value, isOverride(node), new ASTNodeReference(TapestryModule.this, TapestryModule.this.getModuleClass(), node), symbolProvider)); } else if (value instanceof InjectedDeclaration) { // @Symbol? System.out.println(value); } else if (value == null) { providerSymbols.add( new TapestrySymbol( symbolName, null, isOverride(node), new ASTNodeReference(TapestryModule.this, TapestryModule.this.getModuleClass(), node), symbolProvider)); } } }.usesMappedConfiguration()); } } @Override public void openInEditor() { EclipseUtils.openDeclaration(getModuleClass(), null); } }