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