/******************************************************************************* * 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.model; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.jdt.core.ElementChangedEvent; import org.eclipse.jdt.core.IElementChangedListener; import org.eclipse.jdt.core.IJavaElementDelta; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.springframework.ide.eclipse.core.SpringCore; import org.springframework.ide.eclipse.core.internal.model.resources.ISpringResourceChangeEvents; import org.springframework.ide.eclipse.core.internal.model.resources.SpringResourceChangeListener; import org.springframework.ide.eclipse.core.internal.project.SpringProjectContributionManager; import org.springframework.ide.eclipse.core.java.JdtUtils; import org.springframework.ide.eclipse.core.model.AbstractModel; import org.springframework.ide.eclipse.core.model.IModelElement; import org.springframework.ide.eclipse.core.model.ISpringModel; import org.springframework.ide.eclipse.core.model.ISpringProject; import org.springframework.ide.eclipse.core.model.ModelChangeEvent.Type; import org.springframework.util.ObjectUtils; import org.springsource.ide.eclipse.commons.core.SpringCoreUtils; /** * This model manages instances of {@link IProject}s. It's populated from Eclipse's current workspace and receives * {@link IResourceChangeEvent}s for workspaces changes. * <p> * The single instance of {@link ISpringModel} is available from the static method {@link SpringCore#getModel()}. * @author Torsten Juergeleit * @author Christian Dupuis * @author Martin Lippert * @since 2.0 */ public class SpringModel extends AbstractModel implements ISpringModel { private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock(); /** * The table of Spring projects (synchronized for concurrent access) */ protected Map<IProject, ISpringProject> projects; private IResourceChangeListener workspaceListener; private IElementChangedListener classpathListener; public SpringModel() { super(null, ISpringModel.ELEMENT_NAME); projects = new ConcurrentHashMap<IProject, ISpringProject>(); } @Override public IModelElement[] getElementChildren() { try { r.lock(); Collection<ISpringProject> elements = projects.values(); return elements.toArray(new IModelElement[elements.size()]); } finally { r.unlock(); } } public void startup() { try { w.lock(); // Load all projects projects.clear(); for (IProject project : SpringCoreUtils.getSpringProjects()) { ISpringProject proj = new SpringProject(this, project); projects.put(project, proj); } } finally { w.unlock(); } // Add a ResourceChangeListener to the Eclipse Workspace workspaceListener = new SpringResourceChangeListener(new ResourceChangeEventHandler()); IWorkspace workspace = ResourcesPlugin.getWorkspace(); workspace.addResourceChangeListener(workspaceListener, SpringResourceChangeListener.LISTENER_FLAGS); classpathListener = new ClasspathChangedListener(); JavaCore.addElementChangedListener(classpathListener); } public void shutdown() { // Remove the ResourceChangeListener from the Eclipse Workspace IWorkspace workspace = ResourcesPlugin.getWorkspace(); workspace.removeResourceChangeListener(workspaceListener); workspaceListener = null; JavaCore.removeElementChangedListener(classpathListener); classpathListener = null; try { w.lock(); // Remove all projects projects.clear(); } finally { w.unlock(); } } public boolean hasProject(IProject project) { try { r.lock(); return projects.containsKey(project); } finally { r.unlock(); } } public ISpringProject getProject(IProject project) { try { r.lock(); return projects.get(project); } finally { r.unlock(); } } public Set<ISpringProject> getProjects() { try { r.lock(); return Collections.unmodifiableSet(new HashSet<ISpringProject>(projects.values())); } finally { r.unlock(); } } @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof SpringModel)) { return false; } SpringModel that = (SpringModel) other; if (!ObjectUtils.nullSafeEquals(this.projects, that.projects)) return false; return super.equals(other); } @Override public int hashCode() { int hashCode = ObjectUtils.nullSafeHashCode(projects); return getElementType() * hashCode + super.hashCode(); } @Override public String toString() { StringBuffer text = new StringBuffer("Spring model:\n"); synchronized (projects) { int count = projects.size(); for (ISpringProject project : projects.values()) { text.append(project.getElementName()); if (--count > 0) { text.append(", "); } } } return text.toString(); } /** * Internal resource change event handler. */ private class ResourceChangeEventHandler implements ISpringResourceChangeEvents { public boolean isSpringProject(IProject project, int eventType) { try { r.lock(); return projects.get(project) != null; } finally { r.unlock(); } } public void springNatureAdded(IProject project, int eventType) { if (eventType == IResourceChangeEvent.POST_BUILD) { ISpringProject proj = new SpringProject(SpringModel.this, project); try { w.lock(); projects.put(project, proj); } finally { w.unlock(); } // Need ADD here because for the SpringExplorer the according // ISpringProject node has to be appear in the CommonNavigator notifyListeners(proj, Type.ADDED); } } public void springNatureRemoved(IProject project, int eventType) { if (eventType == IResourceChangeEvent.POST_BUILD) { ISpringProject proj = null; try { w.lock(); proj = projects.remove(project); } finally { w.unlock(); } // Need REMOVE here because for the SpringExplorer the according // ISpringProject node has to be disappear in the // CommonNavigator if (proj != null) { notifyListeners(proj, Type.REMOVED); } } } public void projectAdded(IProject project, int eventType) { if (eventType == IResourceChangeEvent.POST_BUILD) { ISpringProject proj = new SpringProject(SpringModel.this, project); try { w.lock(); projects.put(project, proj); } finally { w.unlock(); } notifyListeners(proj, Type.ADDED); } } public void projectOpened(IProject project, int eventType) { if (eventType == IResourceChangeEvent.POST_BUILD) { ISpringProject proj = new SpringProject(SpringModel.this, project); try { w.lock(); projects.put(project, proj); } finally { w.unlock(); } notifyListeners(proj, Type.ADDED); } } public void projectClosed(IProject project, int eventType) { ISpringProject proj = null; try { w.lock(); proj = projects.remove(project); } finally { w.unlock(); } if (proj != null) { notifyListeners(proj, Type.REMOVED); } } public void projectDeleted(IProject project, int eventType) { ISpringProject proj = null; try { w.lock(); proj = projects.remove(project); } finally { w.unlock(); } if (proj != null) { notifyListeners(proj, Type.REMOVED); } } } /** * {@link IElementChangedListener} that listens for changes to the resolved classpath and triggers a project * re-build */ private class ClasspathChangedListener implements IElementChangedListener { public void elementChanged(ElementChangedEvent event) { for (IJavaElementDelta delta : event.getDelta().getAffectedChildren()) { if ((delta.getFlags() & IJavaElementDelta.F_RESOLVED_CLASSPATH_CHANGED) != 0 || (delta.getFlags() & IJavaElementDelta.F_CLASSPATH_CHANGED) != 0) { boolean addedOrRemoved = false; for (IJavaElementDelta classpathDelta : delta.getAffectedChildren()) { if ((classpathDelta.getFlags() & IJavaElementDelta.F_ADDED_TO_CLASSPATH) != 0 || (classpathDelta.getFlags() & IJavaElementDelta.F_REMOVED_FROM_CLASSPATH) != 0 || (classpathDelta.getKind() & IJavaElementDelta.ADDED) != 0 || (classpathDelta.getKind() & IJavaElementDelta.REMOVED) != 0) { addedOrRemoved = true; break; } } if (addedOrRemoved) { SpringProjectContributionManager.classpathChanged(delta.getElement().getJavaProject().getProject().getName()); for (ISpringProject project : SpringCore.getModel().getProjects()) { IJavaProject javaProject = JdtUtils.getJavaProject(project.getProject()); if (javaProject != null && (javaProject.equals(delta.getElement().getJavaProject()) || javaProject .isOnClasspath(delta.getElement()))) { SpringProjectContributionManager.classpathChanged(project.getProject().getName()); // SpringCoreUtils.buildProject(project.getProject()); // workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=375365 // This bug was fixed in Eclipse 3.5.M5 and the call below is bad... causing lots of little 'single project builds' when // importing many projects at once (see https://jira.spring.io/browse/IDE-1329, https://issuetracker.springsource.com/browse/STS-3786). // So commented out. // SpringCoreUtils.buildProject(project.getProject(), "org.eclipse.wst.validation.validationbuilder"); } } } } } } } }