| /******************************************************************************* |
| * 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 |
| } |
| } |