/******************************************************************************* * Copyright (c) 2007, 2013 Spring IDE Developers * 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: * Spring IDE Developers - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.core.internal.project; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.IResourceVisitor; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.SubProgressMonitor; import org.springframework.ide.eclipse.core.SpringCoreUtils; import org.springframework.ide.eclipse.core.internal.model.validation.ValidatorDefinition; import org.springframework.ide.eclipse.core.internal.model.validation.ValidatorDefinitionFactory; import org.springframework.ide.eclipse.core.model.validation.IValidator; import org.springframework.ide.eclipse.core.project.DefaultProjectContributorState; import org.springframework.ide.eclipse.core.project.IProjectBuilder; import org.springframework.ide.eclipse.core.project.IProjectContributionEventListener; import org.springframework.ide.eclipse.core.project.IProjectContributor; import org.springframework.ide.eclipse.core.project.IProjectContributorState; import org.springframework.ide.eclipse.core.project.IProjectContributorStateAware; import org.springframework.ide.eclipse.core.project.ProjectBuilderDefinition; import org.springframework.ide.eclipse.core.project.ProjectBuilderDefinitionFactory; import org.springframework.ide.eclipse.core.project.ProjectContributionEventListenerFactory; /** * Incremental project builder which implements the Strategy GOF pattern. For every modified {@link IResource} within a * Spring project all implementations of the interface {@link IProjectBuilder} provided via the extension point * <code>org.springframework.ide.eclipse.core.builders</code> and the interface {@link IValidator} provided via the * extension point <code>org.springframework.ide.eclipse.core.validators</code> are called. * <p> * This {@link IncrementalProjectBuilder} makes state in form of an instance of {@link IProjectContributorState} for the * {@link IProjectContributor} accessible. This state should be used to store arbitrary state object in order to save * calculation time for subsequent {@link IValidator} or {@link IProjectBuilder}. * <p> * {@link IProjectBuilder} or {@link IValidator} implementations that want to access the state should implement the * {@link IProjectContributorStateAware} interface to a call back with the current state. * * @author Torsten Juergeleit * @author Christian Dupuis * @author Martin Lippert * * @since 2.0 * @see IProjectContributor * @see IProjectBuilder * @see IValidator * @see IProjectContributorState */ public class SpringProjectContributionManager extends IncrementalProjectBuilder { private static Object dummyMapObject = new Object(); private static Map<String, Object> classpathChanged = new ConcurrentHashMap<String, Object>(); /** * indicate that the classpath changed for the given project since the last build * This mirrors the same behavior as the JavaBuilder, which keeps a state between * builds and checks for classpath changes at every build. * * This is triggered by a element change listener in SpringModel that keeps listening * for classpath changes. * * @param projectName The name of the project */ public static void classpathChanged(String projectName) { classpathChanged.put(projectName, dummyMapObject); } /** * {@inheritDoc} */ protected final IProject[] build(final int kind, Map args, final IProgressMonitor monitor) throws CoreException { final IProject project = getProject(); final IResourceDelta delta = getDelta(project); final List<ProjectBuilderDefinition> builderDefinitions = ProjectBuilderDefinitionFactory .getProjectBuilderDefinitions(); final List<ValidatorDefinition> validatorDefinitions = ValidatorDefinitionFactory.getValidatorDefinitions(); final List<IProjectContributionEventListener> listeners = ProjectContributionEventListenerFactory .getProjectContributionEventListeners(); // Set up the state object final IProjectContributorState state = prepareState(project, builderDefinitions, validatorDefinitions); // check for classpath changes (that require a full build) Object removed = classpathChanged.remove(project.getName()); final int buildKind = removed != null ? IncrementalProjectBuilder.FULL_BUILD : kind; // Fire start event on listeners for (final IProjectContributionEventListener listener : listeners) { execute(new SafeExecutableWithMonitor() { public void execute(IProgressMonitor subMonitor) throws Exception { listener.start(buildKind, delta, builderDefinitions, validatorDefinitions, state, project, subMonitor); } }, monitor); } // At first run all builders for (ProjectBuilderDefinition builderDefinition : builderDefinitions) { if (builderDefinition.isEnabled(project)) { Set<IResource> affectedResources = getAffectedResources(builderDefinition.getProjectBuilder(), project, buildKind, delta); runBuilder(builderDefinition, affectedResources, buildKind, monitor, listeners); } } // Finally run all validators for (ValidatorDefinition validatorDefinition : validatorDefinitions) { if (validatorDefinition.isEnabled(project)) { Set<IResource> affectedResources = getAffectedResources(validatorDefinition.getValidator(), project, buildKind, delta); runValidator(validatorDefinition, affectedResources, buildKind, monitor, listeners); } } // Fire end event on listeners for (final IProjectContributionEventListener listener : listeners) { execute(new SafeExecutableWithMonitor() { public void execute(IProgressMonitor subMonitor) throws Exception { listener.finish(buildKind, delta, builderDefinitions, validatorDefinitions, state, project, subMonitor); } }, monitor); } return null; } /** * Collects all affected resources from the given {@link IResourceDelta} and {@link IProjectContributor}. */ private Set<IResource> getAffectedResources(IProjectContributor contributor, IProject project, int kind, IResourceDelta delta) throws CoreException { Set<IResource> affectedResources; if (delta == null || kind == IncrementalProjectBuilder.FULL_BUILD) { ResourceTreeVisitor visitor = new ResourceTreeVisitor(contributor); project.accept(visitor); affectedResources = visitor.getResources(); } else { ResourceDeltaVisitor visitor = new ResourceDeltaVisitor(contributor, kind); delta.accept(visitor); affectedResources = visitor.getResources(); } return affectedResources; } /** * Instantiate the {@link IProjectContributorState} object. The state object is then passed to any * {@link IProjectBuilder} and {@link IValidator} that implements the {@link IProjectContributorStateAware} * interface. * <p> * This implementation creates an instance of {@link DefaultProjectContributorState}. */ private IProjectContributorState prepareState(IProject project, List<ProjectBuilderDefinition> builderDefinitions, List<ValidatorDefinition> validatorDefinitions) { IProjectContributorState context = new DefaultProjectContributorState(); context.hold(project); for (ProjectBuilderDefinition builderDefinition : builderDefinitions) { if (builderDefinition.isEnabled(project) && builderDefinition.getProjectBuilder() instanceof IProjectContributorStateAware) { ((IProjectContributorStateAware) builderDefinition.getProjectBuilder()) .setProjectContributorState(context); } } for (ValidatorDefinition validatorDefinition : validatorDefinitions) { if (validatorDefinition.isEnabled(project) && validatorDefinition.getValidator() instanceof IProjectContributorStateAware) { ((IProjectContributorStateAware) validatorDefinition.getValidator()) .setProjectContributorState(context); } } return context; } /** * Runs all given {@link IProjectBuilder} in the order as they are given in the set. */ private void runBuilder(final ProjectBuilderDefinition builderDefinition, final Set<IResource> affectedResources, final int kind, IProgressMonitor monitor, final List<IProjectContributionEventListener> listeners) { for (final IProjectContributionEventListener listener : listeners) { execute(new SafeExecutableWithMonitor() { @SuppressWarnings("deprecation") public void execute(IProgressMonitor subMonitor) throws Exception { listener.startContributor(builderDefinition.getProjectBuilder(), affectedResources, subMonitor); listener.startProjectBuilder(builderDefinition, affectedResources, subMonitor); } }, monitor); } execute(new SafeExecutableWithMonitor() { public void execute(IProgressMonitor subMonitor) throws Exception { builderDefinition.getProjectBuilder().build(affectedResources, kind, subMonitor); } }, monitor); for (final IProjectContributionEventListener listener : listeners) { execute(new SafeExecutableWithMonitor() { @SuppressWarnings("deprecation") public void execute(IProgressMonitor subMonitor) throws Exception { listener.finishContributor(builderDefinition.getProjectBuilder(), affectedResources, subMonitor); listener.finishProjectBuilder(builderDefinition, affectedResources, subMonitor); } }, monitor); } } /** * Runs all given {@link IValidator} in the order as they are given in the set. */ private void runValidator(final ValidatorDefinition validatorDefinition, final Set<IResource> affectedResources, final int kind, IProgressMonitor monitor, List<IProjectContributionEventListener> listeners) { for (final IProjectContributionEventListener listener : listeners) { execute(new SafeExecutableWithMonitor() { @SuppressWarnings("deprecation") public void execute(IProgressMonitor subMonitor) throws Exception { listener.startContributor(validatorDefinition.getValidator(), affectedResources, subMonitor); listener.startValidator(validatorDefinition, affectedResources, subMonitor); } }, monitor); } execute(new SafeExecutableWithMonitor() { public void execute(IProgressMonitor subMonitor) throws Exception { validatorDefinition.getValidator().validate(affectedResources, kind, subMonitor); } }, monitor); for (final IProjectContributionEventListener listener : listeners) { execute(new SafeExecutableWithMonitor() { @SuppressWarnings("deprecation") public void execute(IProgressMonitor subMonitor) throws Exception { listener.finishContributor(validatorDefinition.getValidator(), affectedResources, subMonitor); listener.finishValidator(validatorDefinition, affectedResources, subMonitor); } }, monitor); } } protected IProgressMonitor createProgressMonitor(IProgressMonitor monitor) { return new SubProgressMonitor(monitor, 1); } protected void execute(final SafeExecutableWithMonitor executable, IProgressMonitor monitor) { final IProgressMonitor subMonitor = createProgressMonitor(monitor); ISafeRunnable code = new ISafeRunnable() { public void handleException(Throwable e) { // nothing to do - exception is already logged } public void run() throws Exception { executable.execute(subMonitor); } }; SafeRunner.run(code); subMonitor.done(); } protected interface SafeExecutableWithMonitor { void execute(IProgressMonitor monitor) throws Exception; } /** * Create a list of affected resources from a resource delta. */ public static class ResourceDeltaVisitor implements IResourceDeltaVisitor { private IProjectContributor contributor; private int kind = -1; private Set<IResource> resources; public ResourceDeltaVisitor(IProjectContributor builder, int kind) { this.contributor = builder; this.resources = new LinkedHashSet<IResource>(); this.kind = kind; } public Set<IResource> getResources() { return resources; } public boolean visit(IResourceDelta aDelta) throws CoreException { boolean visitChildren = false; IResource resource = aDelta.getResource(); if (resource instanceof IProject) { // Only check projects with Spring beans nature visitChildren = SpringCoreUtils.isSpringProject(resource); if (visitChildren) { resources.addAll(contributor.getAffectedResources(resource, kind, aDelta.getKind())); } } else if (resource instanceof IFolder) { resources.addAll(contributor.getAffectedResources(resource, kind, aDelta.getKind())); visitChildren = true; } else if (resource instanceof IFile) { switch (aDelta.getKind()) { case IResourceDelta.ADDED: case IResourceDelta.CHANGED: resources.addAll(contributor.getAffectedResources(resource, kind, aDelta.getKind())); visitChildren = true; break; case IResourceDelta.REMOVED: resources.addAll(contributor.getAffectedResources(resource, kind, aDelta.getKind())); break; } } return visitChildren; } } /** * Create a list of affected resources from a resource tree. */ public static class ResourceTreeVisitor implements IResourceVisitor { private IProjectContributor contributor; private Set<IResource> resources; public ResourceTreeVisitor(IProjectContributor builder) { this.contributor = builder; this.resources = new LinkedHashSet<IResource>(); } public Set<IResource> getResources() { return resources; } public boolean visit(IResource resource) throws CoreException { if (resource instanceof IFile) { resources.addAll(contributor.getAffectedResources(resource, IncrementalProjectBuilder.FULL_BUILD, IResourceDelta.CHANGED)); } else if (resource instanceof IProject) { resources.addAll(contributor.getAffectedResources(resource, IncrementalProjectBuilder.FULL_BUILD, IResourceDelta.CHANGED)); } return true; } } }