blob: d2b98f466296f1fa348893ace95d0d64f9f00bb5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2008 Oracle 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:
* Oracle Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jst.jsf.common.internal.resource;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jst.jsf.common.internal.managedobject.IManagedObject;
import org.eclipse.jst.jsf.common.internal.managedobject.ObjectManager;
import org.eclipse.jst.jsf.common.internal.resource.ResourceLifecycleEvent.EventType;
import org.eclipse.jst.jsf.common.internal.resource.ResourceLifecycleEvent.ReasonType;
/**
* An object manager that manages a single instanceof an IManagedObject per
* resource. The manager takes care of ensuring that a managed object is
* properly disposed when a resource lifecycle event renders it inaccessible
* (i.e file is deleted, project is closed or delete).
*
* @author cbateman
*
* @param <RESOURCE>
* @param <MANAGEDOBJECT>
*/
public abstract class ResourceSingletonObjectManager<MANAGEDOBJECT extends IManagedObject, RESOURCE extends IResource>
extends ObjectManager<MANAGEDOBJECT, RESOURCE>
{
// lazily initialized
private LifecycleListener _lifecycleListener;
final Map<RESOURCE, ManagedResourceObject<MANAGEDOBJECT>> _perResourceObjects;
private final IWorkspace _workspace;
/**
* Default constructor
* @param workspace
*/
protected ResourceSingletonObjectManager(final IWorkspace workspace)
{
_workspace = workspace;
_perResourceObjects = new HashMap<RESOURCE, ManagedResourceObject<MANAGEDOBJECT>>();
}
/**
* @return the workspace
*/
protected final IWorkspace getWorkspace()
{
return _workspace;
}
/**
* @return an unmodifiable view on the map of currently managed objects keyed
* by the resource they are mapped to.
*/
protected final Map<RESOURCE, ManagedResourceObject<MANAGEDOBJECT>> getPerResourceObjects()
{
return Collections.unmodifiableMap(_perResourceObjects);
}
@Override
public final MANAGEDOBJECT getInstance(final RESOURCE key)
throws ManagedObjectException
{
assertNotDisposed();
IAdaptable stateObject = null;
try
{
stateObject = unsafeRunBeforeGetInstance(key);
synchronized(this)
{
runBeforeGetInstance(key);
ManagedResourceObject managedResObject = _perResourceObjects.get(key);
if (managedResObject == null)
{
final MANAGEDOBJECT managedObject = createNewInstance(key);
if (managedObject == null)
{
throw new ManagedObjectException(
"No object available for resource"); //$NON-NLS-1$
}
managedResObject = manageResource(key, managedObject);
}
runAfterGetInstance(key);
return (MANAGEDOBJECT) managedResObject.getManagedObject();
}
}
finally
{
unsafeRunAfterGetInstance(key, stateObject);
}
}
/**
* @param resource
* @return a new instance of T associated with S. This operation must not
* cache T: a brand new instance is always required. getInstance
* will perform caching and resource listening.
*/
protected abstract MANAGEDOBJECT createNewInstance(RESOURCE resource);
/**
* @param resource
*/
protected void runBeforeGetInstance(final RESOURCE resource)
{
// do nothing by default
}
/**
* @param resource
*/
protected void runAfterGetInstance(final RESOURCE resource)
{
// do nothing by default
}
/**
* Callback run outside of synchronized code block in getInstance
* @param resource
* @return A state object to be passed to unsafeRunAfterGetInstance, or null.
*/
protected IAdaptable unsafeRunBeforeGetInstance(final RESOURCE resource)
{
// do nothing by default
return null;
}
/**
* Callback run outside of synchronized code block in getInstance
* @param resource
* @param adaptable State object returned from unsafeRunBeforeGetInstance call, may be null.
*/
protected void unsafeRunAfterGetInstance(final RESOURCE resource, final IAdaptable adaptable)
{
// do nothing by default
}
/**
* @param resource
* @return true if there already exists a managed object associated with
* the resource
*/
public synchronized boolean isInstance(RESOURCE resource)
{
assertNotDisposed();
return _perResourceObjects.containsKey(resource);
}
/**
* @return a copy of the current set of RESOURCE object keys that we
* are managing singletons for. Collection is mutable, but as a copy,
* changes to it do not effect thie object manager.
*/
public synchronized Collection<RESOURCE> getManagedResources()
{
assertNotDisposed();
return new HashSet(_perResourceObjects.keySet());
}
/**
* Should be called by concrete classes to indicate they have created a new
* managed object for resource, for which they want to track lifecycle
* changes.
*
* @param resource
* @param managedObject
*/
private synchronized ManagedResourceObject manageResource(final RESOURCE resource,
final MANAGEDOBJECT managedObject)
{
final LifecycleListener listener = lazilyGetLifecycleListener();
listener.addResource(resource);
final MyLifecycleEventListener<RESOURCE, MANAGEDOBJECT> eventListener =
new MyLifecycleEventListener<RESOURCE, MANAGEDOBJECT>(
this, managedObject, resource);
listener.addListener(eventListener);
final ManagedResourceObject<MANAGEDOBJECT> managedResourceObject = new ManagedResourceObject<MANAGEDOBJECT>(
managedObject, eventListener);
_perResourceObjects.put(resource, managedResourceObject);
return managedResourceObject;
}
/**
* Stop managing the resource. If resource is the last one, the resource
* change listener will be removed (it will be added again when next
* manageResource is called).
*
* @param resource
* @return the managed object that has just be disassociated from the resource.
* The object is not disposed, destroyed or checkpointed before being returned.
*/
protected final synchronized MANAGEDOBJECT unmanageResource(final RESOURCE resource)
{
final ManagedResourceObject managedResourceObject =
_perResourceObjects.remove(resource);
final LifecycleListener listener = lazilyGetLifecycleListener();
if (managedResourceObject != null)
{
listener.removeListener(managedResourceObject.getEventListener());
}
listener.removeResource(resource);
return (MANAGEDOBJECT) managedResourceObject.getManagedObject();
}
/**
* Call to register a listener
*
* @param listener
*/
protected final void addLifecycleEventListener(
final IResourceLifecycleListener listener)
{
assertNotDisposed();
final LifecycleListener lifecycleListener = lazilyGetLifecycleListener();
lifecycleListener.addListener(listener);
}
/**
* Call to remove a listener
*
* @param listener
*/
protected final void removeLifecycleEventListener(
final IResourceLifecycleListener listener)
{
final LifecycleListener lifecycleListener = lazilyGetLifecycleListener();
lifecycleListener.removeListener(listener);
}
/**
* Add additional resources to the set to listen to.
*
* @param res
*/
protected final void addResource(final IResource res)
{
final LifecycleListener lifecycleListener = lazilyGetLifecycleListener();
lifecycleListener.addResource(res);
}
/**
* Remove a resource that is being listened to. Must not be used to remove
* internally added resources (i.e. only use this if you called addResource(res).
*
* @param res
*/
protected final void removeResource(final IResource res)
{
synchronized(this)
{
if (_perResourceObjects.keySet().contains(res))
{
throw new IllegalArgumentException("Can't remove managed resources with this method"); //$NON-NLS-1$
}
}
final LifecycleListener lifecycleListener = lazilyGetLifecycleListener();
lifecycleListener.removeResource(res);
}
private synchronized LifecycleListener lazilyGetLifecycleListener()
{
if (_lifecycleListener == null)
{
_lifecycleListener = new LifecycleListener(_workspace);
}
return _lifecycleListener;
}
/**
* @author cbateman
*
* @param <MANAGEDOBJECT>
*/
protected final static class ManagedResourceObject<MANAGEDOBJECT extends IManagedObject>
{
private final MANAGEDOBJECT _managedObject;
private final MyLifecycleEventListener _eventListener;
private ManagedResourceObject(final MANAGEDOBJECT managedObject,
final MyLifecycleEventListener eventListener)
{
_managedObject = managedObject;
_eventListener = eventListener;
}
/**
* @return the managed object
*/
public MANAGEDOBJECT getManagedObject()
{
return _managedObject;
}
/**
* @return the event listener
*/
public MyLifecycleEventListener getEventListener()
{
return _eventListener;
}
}
private static class MyLifecycleEventListener<RESOURCE extends IResource, MANAGEDOBJECT extends IManagedObject> implements
IResourceLifecycleListener
{
private final RESOURCE _resource;
private final MANAGEDOBJECT _managedObject;
private final ResourceSingletonObjectManager<MANAGEDOBJECT, RESOURCE> _target;
private MyLifecycleEventListener(final ResourceSingletonObjectManager<MANAGEDOBJECT, RESOURCE> target,
final MANAGEDOBJECT managedObject,
final RESOURCE resource)
{
_resource = resource;
_managedObject = managedObject;
_target = target;
}
public EventResult acceptEvent(final ResourceLifecycleEvent event)
{
final EventResult result = EventResult.getDefaultEventResult();
// not interested
if (!_resource.equals(event.getAffectedResource()))
{
return EventResult.getDefaultEventResult();
}
if (event.getEventType() == EventType.RESOURCE_INACCESSIBLE)
{
try
{
if (event.getReasonType() == ReasonType.RESOURCE_DELETED
|| event.getReasonType() == ReasonType.RESOURCE_PROJECT_DELETED)
{
_managedObject.destroy();
}
else
{
_managedObject.dispose();
}
}
// dispose/destroy is external code out our control, so make sure
// unmanage gets called if it blows up.
finally
{
_target.unmanageResource(_resource);
}
}
return result;
}
}
/**
* Unmanages all resources and calls checkpoint and dispose on all managed
* objects. After this call, other methods my throw exception is called.
*
* Sub-class may override, but should always call dispose after disposing
* their own specialized state.
*/
@Override
public void dispose()
{
if (_isDisposed.compareAndSet(false, true))
{
// TODO: implement a better lock strategy on resource manager
synchronized (this)
{
Map<RESOURCE, ManagedResourceObject<MANAGEDOBJECT>> copy = new HashMap<RESOURCE, ManagedResourceObject<MANAGEDOBJECT>>(
getPerResourceObjects());
for (Map.Entry<RESOURCE, ManagedResourceObject<MANAGEDOBJECT>> entry : copy.entrySet())
{
RESOURCE res = entry.getKey();
MANAGEDOBJECT unmanagedResource = unmanageResource(res);
unmanagedResource.checkpoint();
unmanagedResource.dispose();
}
_perResourceObjects.clear();
if (_lifecycleListener != null)
{
_lifecycleListener.dispose();
}
}
}
}
@Override
public void destroy()
{
// do nothing by default
}
@Override
public void checkpoint()
{
// do nothing by default
}
}