/*******************************************************************************
* Copyright (c) 2009, 2014 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.beans.core.internal.model.validation;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
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.SubProgressMonitor;
import org.springframework.ide.eclipse.beans.core.BeansCorePlugin;
import org.springframework.ide.eclipse.beans.core.BeansCoreUtils;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansModel;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansModelUtils;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfig;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfigSet;
import org.springframework.ide.eclipse.beans.core.model.IBeansImport;
import org.springframework.ide.eclipse.beans.core.model.IBeansProject;
import org.springframework.ide.eclipse.beans.core.model.IImportedBeansConfig;
import org.springframework.ide.eclipse.beans.core.model.IReloadableBeansConfig;
import org.springframework.ide.eclipse.core.internal.model.validation.ValidatorDefinition;
import org.springframework.ide.eclipse.core.java.ITypeStructureCache;
import org.springframework.ide.eclipse.core.java.JdtUtils;
import org.springframework.ide.eclipse.core.java.TypeStructureState;
import org.springframework.ide.eclipse.core.model.ModelChangeEvent.Type;
import org.springframework.ide.eclipse.core.project.IProjectContributionEventListener;
import org.springframework.ide.eclipse.core.project.IProjectContributorState;
import org.springframework.ide.eclipse.core.project.ProjectBuilderDefinition;
import org.springframework.ide.eclipse.core.project.ProjectContributionEventListenerAdapter;
import org.springsource.ide.eclipse.commons.core.SpringCoreUtils;
/**
* {@link IProjectContributionEventListener} implementation that handles resetting of {@link IBeansConfig}s based on
* changes to the resource tree.
* @author Christian Dupuis
* @author Martin Lippert
* @since 2.2.5
*/
public class BeansConfigReloadingProjectContributionEventListener extends ProjectContributionEventListenerAdapter {
private TypeStructureState structureState = null;
/** Internal cache of {@link IBeansConfig} instances that should be reloaded */
private final Set<IBeansConfig> configs = new HashSet<IBeansConfig>();
/**
* {@inheritDoc}
*/
@Override
public void start(int kind, IResourceDelta delta, List<ProjectBuilderDefinition> builderDefinitions,
List<ValidatorDefinition> validatorDefinitions, IProjectContributorState state, IProject project,
IProgressMonitor monitor) {
// Get the type structure cache for this build
structureState = state.get(TypeStructureState.class);
if (structureState == null) {
structureState = new TypeStructureState();
}
try {
if (kind != IncrementalProjectBuilder.FULL_BUILD) {
if (delta == null) {
ResourceTreeVisitor visitor = new ResourceTreeVisitor();
project.accept(visitor);
}
else {
ResourceDeltaVisitor visitor = new ResourceDeltaVisitor();
delta.accept(visitor);
}
}
else {
// Reset all for full clean build
IBeansProject beansProject = BeansCorePlugin.getModel().getProject(project);
if (beansProject != null) {
configs.addAll(beansProject.getConfigs());
}
}
}
catch (CoreException e) {
BeansCorePlugin.log(e);
}
// Trigger reloading and reload before validation infrastructure kicks in
if (configs.size() > 0) {
IProgressMonitor subMonitor = new SubProgressMonitor(monitor, 1);
subMonitor.beginTask("Initializing Spring Model", configs.size());
for (IBeansConfig config : configs) {
subMonitor.subTask("Loading '" + config.getElementResource().getFullPath().toString().substring(1)
+ "'");
if (config instanceof IReloadableBeansConfig) {
((IReloadableBeansConfig) config).reload();
config.getBeans();
subMonitor.worked(1);
}
}
subMonitor.done();
}
}
/**
* {@inheritDoc}
*/
@Override
public void finish(int kind, IResourceDelta delta, List<ProjectBuilderDefinition> builderDefinitions,
List<ValidatorDefinition> validatorDefinitions, IProjectContributorState state, IProject project) {
// Send update event
if (configs.size() > 0) {
IBeansProject beansProject = BeansCorePlugin.getModel().getProject(project);
if (beansProject != null) {
((BeansModel) BeansCorePlugin.getModel()).notifyListeners(beansProject, Type.CHANGED);
}
}
// Make sure to clear out the cache for next invocation
configs.clear();
}
/**
* Check if the given <code>resource</code> affects the {@link IBeansConfig}s.
* <p>
* If that is the case the {@link IBeansConfig} and all configs from {@link IBeansConfigSet}s are reset.
*/
private void checkResource(IResource resource) {
// Only reset if the resource represents a Java source file and the types have structural changes
if (resource.getName().endsWith(JdtUtils.JAVA_FILE_EXTENSION)
&& structureState.hasStructuralChanges(resource, ITypeStructureCache.FLAG_ANNOTATION
| ITypeStructureCache.FLAG_ANNOTATION_VALUE)) {
// Reset configs that use component-scanning and annotation-config
for (IBeansProject beansProject : BeansCorePlugin.getModel().getProjects()) {
if (JdtUtils.isJavaProject(beansProject.getProject())
&& JdtUtils.getJavaProject(beansProject.getProject()).isOnClasspath(resource)) {
for (IBeansConfig config : beansProject.getConfigs()) {
if (config.doesAnnotationScanning()) {
propagateToConfigsFromConfigSet(config, false);
}
}
}
}
}
else if (BeansCoreUtils.isBeansConfig(resource, true)) {
IBeansConfig bc = BeansCorePlugin.getModel().getConfig((IFile) resource, true);
// Resources are loaded by isBeansConfig from the top; so the resourceChanged check here is not sufficient
if (bc.resourceChanged()) {
if (bc instanceof IImportedBeansConfig) {
propagateToConfigsFromConfigSet(BeansModelUtils.getParentOfClass(bc, IBeansConfig.class), false);
}
else {
propagateToConfigsFromConfigSet(bc, false);
}
}
else {
propagateToConfigsFromConfigSet(bc, true);
}
}
}
private void propagateToConfigsFromConfigSet(IBeansConfig config, boolean onlyImportsCheck) {
// Add config to make sure that in case on config set is configured
if (!onlyImportsCheck) {
configs.add(config);
}
for (IBeansProject beansProject : BeansCorePlugin.getModel().getProjects()) {
if (!onlyImportsCheck) {
for (IBeansConfigSet configSet : beansProject.getConfigSets()) {
if (configSet.hasConfig((IFile) config.getElementResource())) {
configs.addAll(configSet.getConfigs());
}
}
}
for (IBeansConfig bc : beansProject.getConfigs()) {
for (IBeansImport beansImport : bc.getImports()) {
for (IImportedBeansConfig importedBeansConfig : beansImport.getImportedBeansConfigs()) {
if (config.getElementResource().equals(importedBeansConfig.getElementResource())) {
configs.add(bc);
}
}
}
}
}
}
/**
* Create a list of affected resources from a resource delta.
*/
class ResourceDeltaVisitor implements IResourceDeltaVisitor {
private Set<IResource> resources;
public ResourceDeltaVisitor() {
this.resources = new LinkedHashSet<IResource>();
}
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);
}
else if (resource instanceof IFolder) {
visitChildren = true;
}
else if (resource instanceof IFile) {
switch (aDelta.getKind()) {
case IResourceDelta.ADDED:
case IResourceDelta.CHANGED:
checkResource(resource);
visitChildren = true;
break;
case IResourceDelta.REMOVED:
checkResource(resource);
break;
}
}
return visitChildren;
}
}
/**
* Create a list of affected resources from a resource tree.
*/
class ResourceTreeVisitor implements IResourceVisitor {
private Set<IResource> resources;
public ResourceTreeVisitor() {
this.resources = new LinkedHashSet<IResource>();
}
public Set<IResource> getResources() {
return resources;
}
public boolean visit(IResource resource) throws CoreException {
if (resource instanceof IFile) {
checkResource(resource);
}
return true;
}
}
}