package org.eclipse.jst.jsf.common.internal.resource; import java.util.Collection; import java.util.Collections; import java.util.EventObject; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.Platform; import org.eclipse.jst.jsf.common.JSFCommonPlugin; /** * * @author cbateman * @param <EVENTTYPE> * @param <LISTENERTYPE> * @param <LIFECYCLEOBJECT> * */ public abstract class AbstractLifecycleListener<EVENTTYPE extends EventObject, LISTENERTYPE extends ILifecycleListener<EVENTTYPE>, LIFECYCLEOBJECT> extends ImmutableLifecycleListener<LISTENERTYPE> { /** * Exception (non-localized) error message thrown when a null object is * added. */ protected static final String CANNOT_ADD_NULL_RESOURCE = "Cannot add null object"; //$NON-NLS-1$ static final boolean TRACE_EVENTS; static { TRACE_EVENTS = Boolean.valueOf( Platform.getDebugOption(JSFCommonPlugin.PLUGIN_ID + "/debug/lifecyclelistener")).booleanValue() //$NON-NLS-1$ || Boolean.valueOf(System.getProperty("org.eclipse.jst.jsf.common/debug/lifecyclelistener")).booleanValue(); //$NON-NLS-1$ } private final CopyOnWriteArrayList<LISTENERTYPE> _listeners = new CopyOnWriteArrayList<LISTENERTYPE>(); private final CopyOnWriteArrayList<LIFECYCLEOBJECT> _lifecycleObjects = new CopyOnWriteArrayList<LIFECYCLEOBJECT>(); private final AtomicBoolean _isDisposed = new AtomicBoolean(false); /** * 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 Throws * {@link NullPointerException} if listener == null * * @param listener */ @Override public void addListener(final LISTENERTYPE listener) { if (isDisposed()) { throw new IllegalStateException(); } if (listener == null) { throw new NullPointerException("Cannot pass null listener"); //$NON-NLS-1$ } _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 */ @Override public void removeListener(final LISTENERTYPE listener) { if (isDisposed()) { throw new IllegalStateException(); } _listeners.remove(listener); } /** * @param event */ protected void fireLifecycleEvent(final EVENTTYPE event) { boolean disposeAfter = false; if (TRACE_EVENTS) { System.err.println(event); } // NOTE: must use iterator through _listeners so that // CopyOnWriteArrayList protects us from concurrent modification for (final LISTENERTYPE listener : _listeners) { final EventResult result = listener.acceptEvent(event); disposeAfter |= result.getDisposeAfterEvent(); } if (disposeAfter) { dispose(); } } /** * @return true if the listener has been disposed */ public boolean isDisposed() { return _isDisposed.get(); } /** * Release the resource change listener */ public final void dispose() { if (_isDisposed.compareAndSet(false, true)) { // ensure that add/removeResource don't cause races to add or // remove the resource listener synchronized (_lifecycleObjects) { // remove first to minimize the chance that the listener will // be triggered during the remainder of dispose removeSystemChangeListener(); _lifecycleObjects.clear(); doDispose(); } } } /** * Sub-class disposal specialization. */ protected void doDispose() { // do nothing by default; sub-classes should override to provide their // own disposal. } /** * @param object */ protected void addLifecycleObject(final LIFECYCLEOBJECT object) { if (object == null) { throw new NullPointerException(CANNOT_ADD_NULL_RESOURCE); } synchronized (_lifecycleObjects) { // don't add any resources if we've disposed before acquiring the // lock if (isDisposed()) { return; } final int preSize = _lifecycleObjects.size(); if (!_lifecycleObjects.contains(object)) { _lifecycleObjects.add(object); } // if the size of the array was 0 // and is now greater, make sure the listener is added if (preSize == 0 && _lifecycleObjects.size() > 0) { addSystemChangeListener(); } } } /** * If there are no longer resources being targeted, the resource change * listener will be removed. * * @param res */ public void removeResource(final IResource res) { synchronized (_lifecycleObjects) { // don't bother with this stuff if we're disposed. if (isDisposed()) { return; } _lifecycleObjects.remove(res); // if there are no longer target resources, // remove the workspace listener if (_lifecycleObjects.size() == 0) { removeSystemChangeListener(); } } } /** * @return an iterable of the lifecycle objects being tracked. */ protected Collection<LIFECYCLEOBJECT> getLifecycleObjects() { return Collections.unmodifiableCollection(_lifecycleObjects); } /** * Add the system change listener that is used to collect events that will * be processed into lifecycle change events. */ protected abstract void addSystemChangeListener(); /** * Remove a system change listener added using addSystemChangeListener */ protected abstract void removeSystemChangeListener(); }