blob: 02b505451588a8b559a086255db92e45ef1faf58 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2006 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
/*
* $$RCSfile: ResourceSetWorkbenchSynchronizer.java,v $$
* $$Revision: 1.9 $$ $$Date: 2010/05/12 22:47:45 $$
*/
package org.eclipse.jem.util.emf.workbench;
import java.util.*;
import org.eclipse.core.resources.*;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.jem.internal.util.emf.workbench.EMFWorkbenchContextFactory;
import org.eclipse.jem.util.plugin.JEMUtilPlugin;
/**
* Synchronizer on the workbench. It listens for the project to see if it is closed or deleted. If it does it notifies this out.
*
* @since 1.0.0
*/
public class ResourceSetWorkbenchSynchronizer implements IResourceChangeListener {
protected IProject project;
protected ResourceSet resourceSet;
/** Extenders that will be notified after a pre build resource change */
protected QueuingHashSet <ISynchronizerExtender> extenders;
/**
* This HashSet is similar to a regular HashSet except it can be put in
* queuing mode with a call to the {@link #startQueuing()} method.
* While in queuing mode any adds or removes will not be committed to
* the set until a call to {@link #stopQueuing()} is made. This allows
* the QueuingHasSet to be put in queuing mode prior to iterating over
* the contents without needing to worry about changes coming in and
* throwing ConcurrentModificationExceptions.
*
* @author jsholl
*
* @param <E>
*/
protected class QueuingHashSet <E> extends HashSet <E> {
private static final long serialVersionUID = 6959354060950816784L;
private Object lock = new Object();
private boolean queuing = false;
private Set <E> addQueue = null;
private Set removeQueue = null;
private int initialCapacity = 3;
public QueuingHashSet(int capacity) {
super(capacity);
addQueue = new HashSet<E>(capacity);
removeQueue = new HashSet(capacity);
initialCapacity = capacity;
}
public void startQueuing() {
synchronized(lock){
if(queuing){
throw new UnsupportedOperationException("startQueuing may only be called while not already queuing");
}
this.queuing = true;
addQueue.clear();
removeQueue.clear();
}
}
/**
* Returns the set of adds which occurred while in queuing mode.
* @return
*/
public Set <E> stopQueuing() {
synchronized(lock){
if(!queuing){
throw new UnsupportedOperationException("stopQueuing may only be called while queuing");
}
queuing = false;
removeAll(removeQueue);
addAll(addQueue);
if(!addQueue.isEmpty()){
Set <E> queue = addQueue;
addQueue = new HashSet<E>(initialCapacity);
return queue;
}
return Collections.emptySet();
}
}
@Override
public boolean add(E object) {
synchronized (lock) {
if(queuing){
if(contains(object)){
return false;
}
return addQueue.add(object);
} else{
return super.add(object);
}
}
}
@Override
public boolean remove(Object object) {
synchronized (lock) {
if(queuing){
if(contains(object)){
return removeQueue.add(object);
} else {
return false;
}
} else {
return super.remove(object);
}
}
}
@Override
public boolean isEmpty() {
synchronized (lock) {
return super.isEmpty();
}
}
};
/** The delta for this project that will be broadcast to the extenders */
protected IResourceDelta currentProjectDelta;
private int currentEventType = -1;
/**
* Constructor taking a resource set and project.
*
* @param aResourceSet
* @param aProject
*
* @since 1.0.0
*/
public ResourceSetWorkbenchSynchronizer(ResourceSet aResourceSet, IProject aProject) {
resourceSet = aResourceSet;
project = aProject;
if (aResourceSet != null && aResourceSet instanceof ProjectResourceSet)
((ProjectResourceSet) aResourceSet).setSynchronizer(this);
initialize();
}
/**
* Get the project for this synchronizer
*
* @return project
*
* @since 1.0.0
*/
public IProject getProject() {
return project;
}
/*
* @see IResourceChangeListener#resourceChanged(IResourceChangeEvent)
*/
public void resourceChanged(IResourceChangeEvent event) {
currentEventType = event.getType();
currentProjectDelta = null;
if ((currentEventType == IResourceChangeEvent.PRE_CLOSE || currentEventType == IResourceChangeEvent.PRE_DELETE)
&& event.getResource().equals(getProject())) {
notifyExtendersOfClose();
release();
}
}
protected void notifyExtendersIfNecessary() {
if (currentEventType != IResourceChangeEvent.POST_CHANGE || currentProjectDelta == null)
return;
if(extenders != null){
Set <ISynchronizerExtender> extendersToNotify = extenders;
while(!extendersToNotify.isEmpty()){
try{
extenders.startQueuing();
for (Iterator <ISynchronizerExtender> iterator = extendersToNotify.iterator(); iterator.hasNext();) {
ISynchronizerExtender extender = iterator.next();
extender.projectChanged(currentProjectDelta);
}
} finally {
extendersToNotify = extenders.stopQueuing();
}
}
}
}
protected void notifyExtendersOfClose() {
if(extenders != null){
Set <ISynchronizerExtender> extendersToNotify = extenders;
while(!extendersToNotify.isEmpty()){
try{
extenders.startQueuing();
for (Iterator <ISynchronizerExtender> iterator = extendersToNotify.iterator(); iterator.hasNext();) {
ISynchronizerExtender extender = iterator.next();
extender.projectClosed();
}
} finally {
extendersToNotify = extenders.stopQueuing();
}
}
}
}
protected IWorkspace getWorkspace() {
if (getProject() == null)
return ResourcesPlugin.getWorkspace();
return getProject().getWorkspace();
}
protected void initialize() {
getWorkspace().addResourceChangeListener(this,
IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_BUILD);
}
/**
* Dispose of the synchronizer. Called when no longer needed.
*
*
* @since 1.0.0
*/
public void dispose() {
getWorkspace().removeResourceChangeListener(this);
}
/**
* The project is going away so we need to cleanup ourself and the ResourceSet.
*/
protected void release() {
if (JEMUtilPlugin.isActivated()) {
try {
if (resourceSet instanceof ProjectResourceSet)
((ProjectResourceSet) resourceSet).release();
} finally {
EMFWorkbenchContextFactory.INSTANCE.removeCachedProject(getProject());
dispose();
}
}
}
/**
* Add an extender to be notified of events.
*
* @param extender
*
* @since 1.0.0
*/
public void addExtender(ISynchronizerExtender extender) {
if (extenders == null){
extenders = new QueuingHashSet <ISynchronizerExtender>(3);
}
extenders.add(extender);
}
/**
* Remove extender from notification of events.
*
* @param extender
*
* @since 1.0.0
*/
public void removeExtender(ISynchronizerExtender extender) {
if (extenders == null)
return;
extenders.remove(extender);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
public String toString() {
return getClass().getName() + '(' + ((getProject() != null) ? getProject().getName() : "null") + ')'; //$NON-NLS-1$
}
/**
* Tell Synchronizer that a file is about to be saved. This method should be called prior to writing to an IFile from an EMF resource.
* <p>
* Default does nothing, but subclasses can do something.
* </p>
*
* @param aFile
* file about to be saved.
*
* @since 1.0.0
*/
public void preSave(IFile aFile) {
//Default is do nothing
}
}