blob: 0b6015cedf2305c6a10acc6e58e3c4935b0ef393 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006 Oracle Corporation.
* 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:
* Cameron Bateman/Oracle - initial API and implementation
*
********************************************************************************/
package org.eclipse.jst.jsf.designtime.internal.jsp;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.ILock;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jst.jsf.common.JSFCommonPlugin;
import org.eclipse.jst.jsf.common.internal.resource.EventResult;
import org.eclipse.jst.jsf.common.internal.resource.IResourceLifecycleListener;
import org.eclipse.jst.jsf.common.internal.resource.LifecycleListener;
import org.eclipse.jst.jsf.common.internal.resource.ResourceLifecycleEvent;
import org.eclipse.jst.jsf.common.internal.resource.ResourceLifecycleEvent.EventType;
import org.eclipse.jst.jsf.common.internal.resource.ResourceLifecycleEvent.ReasonType;
import org.eclipse.jst.jsf.common.metadata.Trait;
import org.eclipse.jst.jsf.common.metadata.internal.TraitValueHelper;
import org.eclipse.jst.jsf.common.metadata.query.ITaglibDomainMetaDataModelContext;
import org.eclipse.jst.jsf.common.metadata.query.TaglibDomainMetaDataQueryHelper;
import org.eclipse.jst.jsf.context.resolver.structureddocument.IStructuredDocumentContextResolverFactory;
import org.eclipse.jst.jsf.context.resolver.structureddocument.ITaglibContextResolver;
import org.eclipse.jst.jsf.context.structureddocument.IStructuredDocumentContext;
import org.eclipse.jst.jsf.context.structureddocument.IStructuredDocumentContextFactory;
import org.eclipse.jst.jsf.context.symbol.IComponentSymbol;
import org.eclipse.jst.jsf.context.symbol.ISymbol;
import org.eclipse.jst.jsf.context.symbol.SymbolFactory;
import org.eclipse.jst.jsf.context.symbol.source.AbstractContextSymbolFactory;
import org.eclipse.jst.jsf.context.symbol.source.ISymbolConstants;
import org.eclipse.jst.jsf.core.internal.JSFCorePlugin;
import org.eclipse.jst.jsf.designtime.DesignTimeApplicationManager;
import org.eclipse.jst.jsf.designtime.context.DTFacesContext;
import org.eclipse.osgi.util.NLS;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Processes a JSP model to determine information of interest about it such
* as what tags are currently in use. Listens to the model and updates it's
* information when the model changes.
*
* @author cbateman
*
*/
public class JSPModelProcessor
{
/**
* Pass to force refresh arguments
*/
public final static boolean FORCE_REFRESH = true;
/**
* Pass to runAfter argument of refresh to indicate there is nothing
* to run when the refresh job completes.
*/
public final static Runnable NO_RUN_AFTER = null;
/**
* Pass to runAfter argument of refresh to indicate that the caller
* should be blocked until the job completes.
*/
public final static Runnable RUN_ON_CURRENT_THREAD = new Runnable()
{
public void run()
{
// do nothing
}
};
/**
* An init-time setting that is used to stop the model processor from
* automatically refreshing when the file it is tracking changes. The can
* only be set once at init is permanent for the static life timpe of
* JSPModelProcessor.
*
* Note that it does not turn off listening for file delete events because
* the singleton management still needs to know if it can dispose of an
* instance.
*/
private final static boolean DISABLE_WKSPACE_CHANGE_REFRESH = System
.getProperty("org.eclipse.jst.jsf.jspmodelprocessor.disable.wkspace.change.refresh") != null; //$NON-NLS-1$
private final static Map<IFile, JSPModelProcessor> RESOURCE_MAP =
new HashMap<IFile, JSPModelProcessor>();
private final static java.util.concurrent.locks.Lock CRITICAL_SECTION =
new ReentrantLock();
private static LifecycleListener LIFECYCLE_LISTENER;
/**
* @param file The file to get the model processor for
* @return the processor for a particular model, creating it if it does not
* already exist
* @throws CoreException if an attempt to get the model associated with file
* fails due to reasons other than I/O problems
*/
public static JSPModelProcessor get(final IFile file) throws CoreException
{
CRITICAL_SECTION.lock();
try
{
if (!file.isAccessible())
{
throw new CoreException(new Status(IStatus.ERROR, JSFCorePlugin.PLUGIN_ID, "File must be accessible")); //$NON-NLS-1$
}
JSPModelProcessor processor = RESOURCE_MAP.get(file);
if (processor == null)
{
if (LIFECYCLE_LISTENER == null)
{
LIFECYCLE_LISTENER = new LifecycleListener(file, ResourcesPlugin.getWorkspace());
}
else
{
LIFECYCLE_LISTENER.addResource(file);
}
processor = new JSPModelProcessor(file,LIFECYCLE_LISTENER);
RESOURCE_MAP.put(file, processor);
}
return processor;
}
finally
{
CRITICAL_SECTION.unlock();
}
}
/**
* Disposes of the JSPModelProcessor associated with model
* @param file the model processor to be disposed
*/
private static void dispose(final IFile file)
{
CRITICAL_SECTION.lock();
try
{
final JSPModelProcessor processor = RESOURCE_MAP.get(file);
if (processor != null)
{
RESOURCE_MAP.remove(file);
if (!processor.isDisposed())
{
processor.dispose();
LIFECYCLE_LISTENER.removeResource(file);
}
}
if (RESOURCE_MAP.isEmpty())
{
// if we no longer have any resources being tracked,
// then dispose the lifecycle listener
LIFECYCLE_LISTENER.dispose();
LIFECYCLE_LISTENER = null;
}
}
finally
{
CRITICAL_SECTION.unlock();
}
}
private final IFile _file;
private LifecycleListener _lifecycleListener;
private IResourceLifecycleListener _resListener;
private volatile boolean _isDisposed;
private Map<Object, ISymbol> _requestMap;
private Map<Object, ISymbol> _sessionMap;
private Map<Object, ISymbol> _applicationMap;
private Map<Object, ISymbol> _noneMap;
private Map<Object, ISymbol> _viewMap;
private Map<Object, ISymbol> _flashMap;
// used to avoid infinite recursion in refresh. Must never be null
private final CountingMutex _lastModificationStampMonitor = new CountingMutex();
/**
* Construct a new JSPModelProcessor for model
*
* @param model
*/
private JSPModelProcessor(final IFile file, final LifecycleListener lifecycleListener)
{
_file = file;
_lifecycleListener = lifecycleListener;
_resListener = new IResourceLifecycleListener()
{
public EventResult acceptEvent(final ResourceLifecycleEvent event)
{
final EventResult result = EventResult.getDefaultEventResult();
// not interested
if (!_file.equals(event.getAffectedResource()))
{
return result;
}
if (event.getEventType() == EventType.RESOURCE_INACCESSIBLE)
{
dispose(_file);
}
else if (event.getEventType() == EventType.RESOURCE_CHANGED)
{
// if the file has changed contents on disk, then
// invoke an unforced refresh of the JSP file
if (event.getReasonType() == ReasonType.RESOURCE_CHANGED_CONTENTS
&& !DISABLE_WKSPACE_CHANGE_REFRESH)
{
refresh(! FORCE_REFRESH, NO_RUN_AFTER);
}
}
return result;
}
};
lifecycleListener.addListener(_resListener);
}
private IDOMModel getModelForFile(final IFile file)
throws CoreException, IOException
{
final IModelManager modelManager =
StructuredModelManager.getModelManager();
final IStructuredModel model = modelManager.getModelForRead(file);
if (model instanceof IDOMModel)
{
return (IDOMModel) model;
}
else if (model != null)
{
// only release from read if we don't find a DOMModelForJSP
// if the model is correct, it will be released in dispose
model.releaseFromRead();
}
JSFCorePlugin.log("Couldn't get valid model for file: "+file.toString(), new Exception()); //$NON-NLS-1$
return null;
}
private void dispose()
{
if (!_isDisposed)
{
// ensure the resource listener is disposed
_lifecycleListener.removeListener(_resListener);
_resListener = null;
_lifecycleListener = null;
if (_requestMap != null)
{
_requestMap.clear();
_requestMap = null;
}
if (_sessionMap != null)
{
_sessionMap.clear();
_sessionMap = null;
}
if (_applicationMap != null)
{
_applicationMap.clear();
_applicationMap = null;
}
if (_noneMap != null)
{
_noneMap.clear();
_noneMap = null;
}
// mark as disposed
_isDisposed = true;
}
}
/**
* @return true if this model processor has been disposed. Disposed
* processors should not be used.
*/
public boolean isDisposed()
{
return _isDisposed;
}
/**
* If isModelDirty() returns true, then it means that a call
* to refresh(false) will trigger a reprocess of the underlying document.
*
* @return true if the underlying JSP model is considered to be dirty
*/
public boolean isModelDirty()
{
final long currentModificationStamp = _file.getModificationStamp();
return _lastModificationStampMonitor.hasChanged(currentModificationStamp);
}
/**
* Refreshes the processor's cache of information from its associated
* JSP file.
*
* @param forceRefresh
* @param runAfter
*/
public void refresh(final boolean forceRefresh, final Runnable runAfter)
{
if (isDisposed())
{
throw new IllegalStateException(
"Processor is disposed for file: " + _file.toString()); //$NON-NLS-1$
}
if (runAfter == RUN_ON_CURRENT_THREAD)
{
try
{
runOnCurrentThread(forceRefresh);
}
catch (final CoreException e)
{
JSFCorePlugin.log(e, "Running JSP model processor"); //$NON-NLS-1$
}
catch (final OperationCanceledException e)
{
// ignore
}
}
else
{
runOnWorkspaceJob(forceRefresh, runAfter);
}
}
private void runOnWorkspaceJob(final boolean forceRefresh, final Runnable runAfter)
{
WorkspaceJob refreshJob = new WorkspaceJob(NLS.bind(Messages
.getString("JSPModelProcessor.0"), _file)) { //$NON-NLS-1$
@Override
public IStatus runInWorkspace(IProgressMonitor monitor)
throws CoreException
{
RefreshRunnable runnable = new RefreshRunnable(forceRefresh);
runnable.run(monitor);
return Status.OK_STATUS;
}
};
refreshJob.setSystem(true);
refreshJob.setRule(ResourcesPlugin.getWorkspace().getRoot());
if (runAfter != null)
{
refreshJob.addJobChangeListener(new JobChangeAdapter()
{
@Override
public void done(final IJobChangeEvent event)
{
runAfter.run();
}
});
}
refreshJob.schedule();
}
private void runOnCurrentThread(final boolean forceRefresh) throws CoreException, OperationCanceledException
{
ResourcesPlugin.getWorkspace().run(new RefreshRunnable(forceRefresh), _file, 0, null);
}
private final class RefreshRunnable implements IWorkspaceRunnable
{
private final boolean _forceRefresh;
public RefreshRunnable(final boolean forceRefresh)
{
_forceRefresh = forceRefresh;
}
public void run(final IProgressMonitor monitor)
throws CoreException
{
synchronized (_lastModificationStampMonitor)
{
if (!_lastModificationStampMonitor.compareAndSetSignalled(false, true))
{
// if this calls succeeds, then this thread has obtained the
// lock already and has called through here before.
// return immediately to ensure that we don't recurse
// infinitely
return;
}
IDOMModel model = null;
try
{
// only refresh if forced or if the underlying file has
// changed
// since the last run
if (_forceRefresh || isModelDirty())
{
model = getModelForFile(_file);
if (model != null)
{
refreshInternal(model);
}
_lastModificationStampMonitor.setModificationStamp(_file.getModificationStamp());
}
}
catch (final IOException e)
{
IStatus status = new Status(IStatus.ERROR, JSFCorePlugin.PLUGIN_ID,"Error refreshing internal model", e); //$NON-NLS-1$
final CoreException e2 = new CoreException(status);
throw e2;
}
// make sure that we unsignal the monitor before releasing the
// mutex
finally
{
_lastModificationStampMonitor.setSignalled(false);
if (model != null)
{
model.releaseFromRead();
}
}
}
}
}
private void refreshInternal(final IDOMModel model)
{
final IStructuredDocumentContext context =
IStructuredDocumentContextFactory.INSTANCE.getContext(model.getStructuredDocument(), -1);
final ITaglibContextResolver taglibResolver =
IStructuredDocumentContextResolverFactory.INSTANCE.getTaglibContextResolver(context);
final IDOMDocument document = model.getDocument();
getApplicationMap().clear();
getRequestMap().clear();
getSessionMap().clear();
if (taglibResolver == null) {
// unusual, but protect against possible NPE
JSFCorePlugin.log(IStatus.ERROR, "Program Error: taglib resolver is null."); //$NON-NLS-1$
return;
}
//long curTime = System.currentTimeMillis();
recurseChildNodes(model, document.getChildNodes(), taglibResolver);
//long netTime = System.currentTimeMillis() - curTime;
//System.out.println("Net time to recurse document: "+netTime);
}
private void recurseChildNodes(final IDOMModel model,
final NodeList nodes,
final ITaglibContextResolver taglibResolver)
{
for (int i = 0; i < nodes.getLength(); i++)
{
final Node child = nodes.item(i);
// process attributes at this node before recursing
processAttributes(model, child, taglibResolver);
recurseChildNodes(model, child.getChildNodes(), taglibResolver);
}
}
private void processAttributes(final IDOMModel model, final Node node,
final ITaglibContextResolver taglibResolver)
{
if (taglibResolver.hasTag(node))
{
final String uri =
taglibResolver.getTagURIForNodeName(node);
final String elementName = node.getLocalName();
for (int i = 0; i < node.getAttributes().getLength(); i++)
{
final Node attribute = node.getAttributes().item(i);
processSymbolContrib(model, uri, elementName, attribute);
processSetsLocale(uri, elementName, attribute);
}
}
}
private void processSymbolContrib(final IDOMModel model, final String uri, final String elementName, final Node attribute)
{
final SymbolContribAggregator aggregator =
SymbolContribAggregator.
create(_file.getProject(), uri, elementName, attribute.getLocalName());
if (aggregator != null)
{
final AbstractContextSymbolFactory factory = aggregator.getFactory();
final String symbolName = attribute.getNodeValue();
if (factory != null)
{
// long curTime = System.currentTimeMillis();
final IStructuredDocumentContext context =
IStructuredDocumentContextFactory.INSTANCE.
getContext(model.getStructuredDocument(),
attribute);
if (factory.supports(context))
{
final List problems = new ArrayList();
final ISymbol symbol =
factory.create(symbolName,
ISymbolConstants.SYMBOL_SCOPE_REQUEST, //TODO:
context,
problems,
// TODO: add meta-data for signature
new AdditionalContextSymbolInfo(aggregator.getStaticType(), aggregator.getValueExpressionAttr()));
// long netTime = System.currentTimeMillis() - curTime;
// System.out.println("Time to process loadBundle: "+netTime);
if (symbol != null)
{
updateMap(symbol, aggregator.getScope());
}
}
}
else
{
final IComponentSymbol componentSymbol =
SymbolFactory.eINSTANCE.createIComponentSymbol();
componentSymbol.setName(symbolName);
updateMap(componentSymbol, aggregator.getScope());
}
}
}
@SuppressWarnings("deprecation")
private void processSetsLocale(final String uri, final String elementName, Node attribute)
{
LocaleSetAggregator aggregator = LocaleSetAggregator.create(_file.getProject(), uri, elementName, attribute.getLocalName());
if (aggregator != null)
{
DesignTimeApplicationManager dtAppMgr =
DesignTimeApplicationManager.getInstance(_file.getProject());
if (dtAppMgr != null)
{
DTFacesContext facesContext = dtAppMgr.getFacesContext(_file);
if (facesContext != null)
{
facesContext.setLocaleString(attribute.getNodeValue());
}
}
}
}
/**
* @param scopeName - one of "request", "session" or "application" or "view" if JSF2.x
* @return an unmodifable map containing all known symbols for
* that scope. If scopeName is not found, returns the empty map.
*/
public Map<Object, ISymbol> getMapForScope(final String scopeName)
{
final Map<Object, ISymbol> map = getMapForScopeInternal(scopeName);
if (map != null)
{
return Collections.unmodifiableMap(map);
}
return Collections.EMPTY_MAP;
}
private void updateMap(final ISymbol symbol, final String scopeName)
{
final Map<Object, ISymbol> map = getMapForScopeInternal(scopeName);
if (map != null)
{
map.put(symbol.getName(), symbol);
}
else
{
Platform.getLog(JSFCorePlugin.getDefault().getBundle()).log(new Status(IStatus.ERROR, JSFCorePlugin.PLUGIN_ID, 0, "Scope not found: "+scopeName, new Throwable())); //$NON-NLS-1$
}
}
private Map<Object, ISymbol> getMapForScopeInternal(final String scopeName)
{
if (ISymbolConstants.SYMBOL_SCOPE_REQUEST_STRING.equals(scopeName))
{
return getRequestMap();
}
else if (ISymbolConstants.SYMBOL_SCOPE_SESSION_STRING.equals(scopeName))
{
return getSessionMap();
}
else if (ISymbolConstants.SYMBOL_SCOPE_APPLICATION_STRING.equals(scopeName))
{
return getApplicationMap();
}
else if (ISymbolConstants.SYMBOL_SCOPE_NONE_STRING.equals(scopeName))
{
return getNoneMap();
}
else if (ISymbolConstants.SYMBOL_SCOPE_VIEW_STRING.equals(scopeName))
{
return getViewMap();
}
else if (ISymbolConstants.SYMBOL_SCOPE_FLASH_STRING.equals(scopeName))
{
return getFlashMap();
}
Platform.getLog(JSFCorePlugin.getDefault().getBundle()).log(new Status(IStatus.ERROR, JSFCorePlugin.PLUGIN_ID, 0, "Scope not found: "+scopeName, new Throwable())); //$NON-NLS-1$
return null;
}
private Map getRequestMap()
{
if (_requestMap == null)
{
_requestMap = new HashMap<Object, ISymbol>();
}
return _requestMap;
}
private Map<Object, ISymbol> getSessionMap()
{
if (_sessionMap == null)
{
_sessionMap = new HashMap<Object, ISymbol>();
}
return _sessionMap;
}
private Map<Object, ISymbol> getApplicationMap()
{
if (_applicationMap == null)
{
_applicationMap = new HashMap<Object, ISymbol>();
}
return _applicationMap;
}
private Map<Object, ISymbol> getNoneMap()
{
if (_noneMap == null)
{
_noneMap = new HashMap<Object, ISymbol>();
}
return _noneMap;
}
private Map<Object, ISymbol> getViewMap()
{
if (_viewMap == null)
{
_viewMap = new HashMap<Object, ISymbol>();
}
return _viewMap;
}
private Map<Object, ISymbol> getFlashMap()
{
if (_flashMap == null)
{
_flashMap = new HashMap<Object, ISymbol>();
}
return _flashMap;
}
/**
* Aggregates the sets-locale meta-data
*
* @author cbateman
*/
private static class LocaleSetAggregator
{
private final static String SETS_LOCALE = "sets-locale"; //$NON-NLS-1$
private static LocaleSetAggregator create(final IProject project,
final String uri,
final String elementName, final String attributeName)
{
final ITaglibDomainMetaDataModelContext mdContext = TaglibDomainMetaDataQueryHelper.createMetaDataModelContext(project, uri);
final Trait trait = TaglibDomainMetaDataQueryHelper.getTrait(mdContext, elementName+"/"+attributeName, SETS_LOCALE); //$NON-NLS-1$
if (TraitValueHelper.getValueAsBoolean(trait))
{
return new LocaleSetAggregator();
}
return null;
}
}
/**
* Aggregates all the symbol contributor meta-data into a single object
*
* @author cbateman
*
*/
private static class SymbolContribAggregator
{
private final static String CONTRIBUTES_VALUE_BINDING =
"contributes-value-binding"; //$NON-NLS-1$
private final static String VALUE_BINDING_SCOPE = "value-binding-scope"; //$NON-NLS-1$
private final static String VALUE_BINDING_SYMBOL_FACTORY =
"value-binding-symbol-factory"; //$NON-NLS-1$
private final static String STATIC_TYPE_KEY = "optional-value-binding-static-type"; //$NON-NLS-1$
private final static String VALUEEXPRESSION_ATTR_NAME_KEY = "optional-value-binding-valueexpr-attr"; //$NON-NLS-1$
/**
* @param attributeName
* @return a new instance only if attributeName is a symbol contributor
*/
private static SymbolContribAggregator create(final IProject project,
final String uri,
final String elementName,
final String attributeName)
{
final String entityKey = elementName+"/"+attributeName; //$NON-NLS-1$
final ITaglibDomainMetaDataModelContext mdContext = TaglibDomainMetaDataQueryHelper.createMetaDataModelContext(project, uri);
Trait trait = TaglibDomainMetaDataQueryHelper.getTrait(mdContext, entityKey, CONTRIBUTES_VALUE_BINDING);
final boolean contribsValueBindings = TraitValueHelper.getValueAsBoolean(trait);
if (contribsValueBindings)
{
String scope = null;
String symbolFactory = null;
trait = TaglibDomainMetaDataQueryHelper.getTrait(mdContext, entityKey, VALUE_BINDING_SCOPE);
scope = TraitValueHelper.getValueAsString(trait);
if (scope != null && !scope.equals("")) //$NON-NLS-1$
{
trait = TaglibDomainMetaDataQueryHelper.getTrait(mdContext, entityKey, VALUE_BINDING_SYMBOL_FACTORY);
symbolFactory = TraitValueHelper.getValueAsString(trait);
}
trait = TaglibDomainMetaDataQueryHelper.getTrait(mdContext, entityKey, STATIC_TYPE_KEY);
String staticType = null;
if (trait != null)
{
staticType = TraitValueHelper.getValueAsString(trait);
}
trait = TaglibDomainMetaDataQueryHelper.getTrait(mdContext, entityKey, VALUEEXPRESSION_ATTR_NAME_KEY);
String valueExprAttr = null;
if (trait != null)
{
valueExprAttr = TraitValueHelper.getValueAsString(trait);
}
return new SymbolContribAggregator(scope, symbolFactory, staticType, valueExprAttr);
}
return null;
}
private final Map<String, String> _metadata = new HashMap<String, String>(4);
SymbolContribAggregator(final String scope, final String factory, final String staticType, final String valueExprAttr)
{
_metadata.put("scope", scope); //$NON-NLS-1$
_metadata.put("factory", factory); //$NON-NLS-1$
_metadata.put("staticType", staticType); //$NON-NLS-1$
_metadata.put("valueExprAttr", valueExprAttr); //$NON-NLS-1$
}
/**
* @return the scope
*/
public String getScope()
{
return _metadata.get("scope"); //$NON-NLS-1$
}
/**
* @return the factory
*/
public AbstractContextSymbolFactory getFactory()
{
return JSFCommonPlugin.getSymbolFactories().get(_metadata.get("factory")); //$NON-NLS-1$
}
public String getStaticType()
{
return _metadata.get("staticType"); //$NON-NLS-1$
}
public String getValueExpressionAttr()
{
return _metadata.get("valueExprAttr"); //$NON-NLS-1$
}
}
private final static class CountingMutex extends Object
{
private long _lastModificationStamp = -1;
private boolean _signalled = false;
private final ILock _lock = Job.getJobManager().newLock();
/**
* Similar to AtomicBoolean.compareAndSet. If the signalled flag
* is the same as expect then update is written to the flag. Otherwise,
* nothing happens.
* @param expect the value of _signalled where update occurs
* @param update the value written to _signalled if _signalled == expect
*
* @return true if the signalled flag was set to update
*/
public boolean compareAndSetSignalled(final boolean expect, final boolean update) {
final boolean[] value = new boolean[1];
safeRun(new Runnable() {
public void run()
{
if (_signalled == expect)
{
_signalled = update;
value[0] = true;
}
else
{
value[0] = false;
}
}});
return value[0];
}
public boolean hasChanged(final long currentModificationStamp)
{
final boolean[] value = new boolean[1];
safeRun(new Runnable() {
public void run()
{
value[0] = (_lastModificationStamp != currentModificationStamp);
}});
return value[0];
}
/**
* @param signalled
*/
public void setSignalled(final boolean signalled) {
safeRun(new Runnable() {
public void run()
{
_signalled = signalled;
}});
}
public void setModificationStamp(final long newValue)
{
safeRun(new Runnable() {
public void run()
{
_lastModificationStamp = newValue;
}});
}
private void safeRun(final Runnable runnable)
{
_lock.acquire();
try
{
runnable.run();
}
finally
{
_lock.release();
}
}
}
}