blob: 465eafc0285d93b77343167610b97171d9341a69 [file] [log] [blame]
package org.eclipse.jst.jsf.common.internal.resource;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jst.jsf.common.internal.ITestTracker;
import org.eclipse.jst.jsf.common.internal.ITestTracker.Event;
import org.eclipse.jst.jsf.common.internal.resource.IResourceLifecycleListener.EventResult;
import org.eclipse.jst.jsf.common.internal.resource.ResourceLifecycleEvent.EventType;
import org.eclipse.jst.jsf.common.internal.resource.ResourceLifecycleEvent.ReasonType;
/**
* Listens to resource changes and fires lifecycle events
*
* @author cbateman
*
*/
public class LifecycleListener implements IResourceChangeListener
{
private final static boolean ENABLE_TEST_TRACKING
= false;
private static long _seqId;
private final CopyOnWriteArrayList<IResource> _resources;
private final CopyOnWriteArrayList<IResourceLifecycleListener> _listeners;
private AtomicBoolean _isDisposed = new AtomicBoolean(false);
private ITestTracker _testTracker; // == null; initialized by setter injection
/**
* Initialize an inactive lifecycle listener. A workspace listener will not
* be installed by this constructor. The object created using this constructor
* will not fire any events until addResource is called at least once to add
* a target resource
*/
public LifecycleListener()
{
_resources = new CopyOnWriteArrayList<IResource>();
_listeners = new CopyOnWriteArrayList<IResourceLifecycleListener>();
}
/**
* Create a new lifecycle listener for the res
* @param res
*/
public LifecycleListener(final IResource res)
{
this();
_resources.add(res);
ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
}
/**
* @param resources
*/
public LifecycleListener(final List<IResource> resources)
{
this();
_resources.addAll(resources);
ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
}
/**
* @param testTracker
*/
public final void setTestTracker(final ITestTracker testTracker)
{
_testTracker = testTracker;
}
/**
* @param res
*/
public void addResource(final IResource res)
{
synchronized(_resources)
{
// don't add any resources if we've disposed before acquiring the lock
if (isDisposed()) return;
final int preSize = _resources.size();
if (!_resources.contains(res))
{
_resources.add(res);
}
// if the size of the array was 0
// and is now greater, make sure the listener is added
if (preSize == 0
&& _resources.size() > 0)
{
ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
}
}
}
/**
* If there are no longer resources being targeted, the resource change
* listener will be removed.
*
* @param res
*/
public void removeResource(final IResource res)
{
synchronized(_resources)
{
// don't bother with this stuff if we're disposed.
if (isDisposed()) return;
_resources.remove(res);
// if there are no longer target resources,
// remove the workspace listener
if (_resources.size() == 0)
{
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
}
}
}
/**
* Release the resource change listener
*/
public void dispose()
{
if (_isDisposed.compareAndSet(false, true))
{
// ensure that add/removeResource don't cause races to add or
// remove the resource listener
synchronized(_resources)
{
// remove first to minimize the chance that the listener will
// be triggered during the remainder of dispose
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
_resources.clear();
}
}
}
/**
* @return true if the listener has been disposed
*/
public boolean isDisposed() {
return _isDisposed.get();
}
/**
* Adds listener to the list of objects registered to receive
* lifecycle events for this resource. Only adds the listener
* if it is not already in the list.
*
* Method is thread-safe and may block the caller
*
* Throws {@link IllegalStateException} if isDisposed() == true
*
* @param listener
*/
public void addListener(final IResourceLifecycleListener listener)
{
if (isDisposed())
{
throw new IllegalStateException();
}
_listeners.addIfAbsent(listener);
}
/**
* Removes listener from the list of registered listeners
*
* Method is thread-safe and may block the caller
*
* Throws {@link IllegalStateException} if isDisposed() == true
*
* @param listener
*/
public void removeListener(final IResourceLifecycleListener listener)
{
if (isDisposed())
{
throw new IllegalStateException();
}
_listeners.remove(listener);
}
public void resourceChanged(final IResourceChangeEvent event)
{
final long seqId = _seqId++;
if (ENABLE_TEST_TRACKING && _testTracker != null)
{
_testTracker.fireEvent(Event.START_TRACKING, seqId, "trackMethod_resourceChanged");
}
assert(!isDisposed());
switch(event.getType())
{
case IResourceChangeEvent.PRE_CLOSE:
{
final IProject proj = (IProject) event.getResource();
// must use iterator to ensure copy on write behaviour
for (final IResource res : _resources)
{
if (proj == res || proj == res.getProject())
{
fireLifecycleEvent(
new ResourceLifecycleEvent(res, EventType.RESOURCE_INACCESSIBLE, ReasonType.RESOURCE_PROJECT_CLOSED));
}
}
}
break;
case IResourceChangeEvent.PRE_DELETE:
{
final IProject proj = (IProject) event.getResource();
// must use iterator to ensure copy on write behaviour
for (final IResource res : _resources)
{
// if the resource being tracked is the resource being deleted,
// then fire a resource delete event
if (proj == res)
{
fireLifecycleEvent(new ResourceLifecycleEvent(res, EventType.RESOURCE_INACCESSIBLE, ReasonType.RESOURCE_DELETED));
}
// if the resource being tracked is a resource in the project being
// deleted, then fire a project deleted event
else if (proj == res.getProject())
{
fireLifecycleEvent(
new ResourceLifecycleEvent(res, EventType.RESOURCE_INACCESSIBLE, ReasonType.RESOURCE_PROJECT_DELETED));
}
}
}
break;
case IResourceChangeEvent.POST_CHANGE:
{
for (final IResource res : _resources)
{
IResourceDelta delta = event.getDelta();
// long seqId2 = _seqId++;
// if (ENABLE_TEST_TRACKING && _testTracker != null)
// {
// _testTracker.fireEvent(Event.START_TRACKING, seqId2, "testFindMember");
// }
// only care about post change events to resources
// that we are tracking
delta = delta.findMember(res.getFullPath());
if (delta != null)
{
visit(delta);
}
// if (ENABLE_TEST_TRACKING && _testTracker != null)
// {
// _testTracker.fireEvent(Event.STOP_TRACKING, seqId2, "testFindMember");
// }
}
}
break;
default:
// do nothing
// we only handle these three
}
if (ENABLE_TEST_TRACKING && _testTracker != null)
{
_testTracker.fireEvent(Event.STOP_TRACKING, seqId, "trackMethod_resourceChanged");
}
}
private void fireLifecycleEvent(final ResourceLifecycleEvent event)
{
boolean disposeAfter = false;
// NOTE: must use iterator through _listeners so that
// CopyOnWriteArrayList protects us from concurrent modification
for (final IResourceLifecycleListener listener : _listeners)
{
final EventResult result = listener.acceptEvent(event);
disposeAfter |= result.getDisposeAfterEvent();
}
if (disposeAfter)
{
dispose();
}
}
private void visit(final IResourceDelta delta)
{
assert (!isDisposed());
final IResource res = delta.getResource();
// the wkspace root is a special case since even though
// it is registered as the target resource, we are interested
// in new projects created in the root
if (res.getType() == IResource.ROOT)
{
handleWorkspaceRoot(delta);
}
switch (delta.getKind())
{
case IResourceDelta.CHANGED:
{
// the contents of the file have changed
if ((delta.getFlags() & IResourceDelta.CONTENT) != 0)
{
fireLifecycleEvent(
new ResourceLifecycleEvent
(res, EventType.RESOURCE_CHANGED
, ReasonType.RESOURCE_CHANGED_CONTENTS));
}
}
break;
case IResourceDelta.REMOVED:
{
fireLifecycleEvent(
new ResourceLifecycleEvent
(res, EventType.RESOURCE_INACCESSIBLE
, ReasonType.RESOURCE_DELETED));
}
break;
}
}
private void handleWorkspaceRoot(final IResourceDelta delta)
{
for (final IResourceDelta childDelta : delta
.getAffectedChildren(IResourceDelta.ADDED))
{
final IResource res = childDelta.getResource();
if ((childDelta.getFlags() & IResourceDelta.OPEN) != 0 &&
// project was just opened
res.getType() == IResource.PROJECT)
{
fireLifecycleEvent(new ResourceLifecycleEvent(res,
EventType.RESOURCE_ADDED, ReasonType.PROJECT_OPENED));
}
}
}
}