| /******************************************************************************* |
| * 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.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jst.jsf.common.JSFCommonPlugin; |
| 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.wst.html.core.internal.document.DOMStyleModelImpl; |
| 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 |
| { |
| 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(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); |
| } |
| 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(IFile file) |
| { |
| CRITICAL_SECTION.lock(); |
| try |
| { |
| 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.size() == 0) |
| { |
| // 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 boolean _isDisposed; |
| private Map<Object, ISymbol> _requestMap; |
| private Map<Object, ISymbol> _sessionMap; |
| private Map<Object, ISymbol> _applicationMap; |
| private Map<Object, ISymbol> _noneMap; |
| private long _lastModificationStamp; |
| |
| // 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) |
| { |
| //_model = getModelForFile(file); |
| //_modelListener = new ModelListener(); |
| //_model.addModelLifecycleListener(_modelListener); |
| _file = file; |
| _lifecycleListener = lifecycleListener; |
| _resListener = new IResourceLifecycleListener() |
| { |
| public EventResult acceptEvent(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) |
| { |
| refresh(false); |
| } |
| } |
| |
| return result; |
| } |
| }; |
| |
| lifecycleListener.addListener(_resListener); |
| |
| // a negative value guarantees that refresh(false) will |
| // force a refresh on the first run |
| _lastModificationStamp = -1; |
| } |
| |
| private DOMStyleModelImpl getModelForFile(final IFile file) |
| throws CoreException, IOException |
| { |
| final IModelManager modelManager = |
| StructuredModelManager.getModelManager(); |
| |
| IStructuredModel model = modelManager.getModelForRead(file); |
| |
| if (model instanceof DOMStyleModelImpl) |
| { |
| return (DOMStyleModelImpl) 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(); |
| } |
| |
| throw new CoreException |
| (new Status(IStatus.ERROR |
| , "org.eclipse.blah" //$NON-NLS-1$ |
| , 0 //$NON-NLS-1$ |
| ,"model not of expected type" //$NON-NLS-1$ |
| , new Throwable())); //$NON-NLS-1$ |
| } |
| |
| 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 _lastModificationStamp != currentModificationStamp; |
| } |
| |
| /** |
| * Updates the internal model |
| * @param forceRefresh -- if true, always refreshes, if false, |
| * then it only refreshes if the file's modification has changed |
| * since the last refresh |
| * @throws IllegalStateException if isDisposed() == true |
| */ |
| public void refresh(final boolean forceRefresh) |
| { |
| if (isDisposed()) |
| { |
| throw new IllegalStateException("Processor is disposed for file: "+_file.toString()); //$NON-NLS-1$ |
| } |
| |
| synchronized(_lastModificationStampMonitor) |
| { |
| if (_lastModificationStampMonitor.isSignalled()) |
| { |
| // 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; |
| } |
| |
| DOMStyleModelImpl model = null; |
| try |
| { |
| _lastModificationStampMonitor.setSignalled(true); |
| |
| |
| // only refresh if forced or if the underlying file has changed |
| // since the last run |
| if (forceRefresh |
| || isModelDirty()) |
| { |
| model = getModelForFile(_file); |
| refreshInternal(model); |
| _lastModificationStamp = _file.getModificationStamp(); |
| } |
| } |
| catch (CoreException e) { |
| JSFCorePlugin.log(new RuntimeException(e), "Error refreshing internal model"); //$NON-NLS-1$ |
| } catch (IOException e) { |
| JSFCorePlugin.log(new RuntimeException(e), "Error refreshing internal model"); //$NON-NLS-1$ |
| } |
| // make sure that we unsignal the monitor before releasing the |
| // mutex |
| finally |
| { |
| if (model != null) |
| { |
| model.releaseFromRead(); |
| } |
| _lastModificationStampMonitor.setSignalled(false); |
| } |
| } |
| } |
| |
| private void refreshInternal(DOMStyleModelImpl model) |
| { |
| final IStructuredDocumentContext context = |
| IStructuredDocumentContextFactory.INSTANCE.getContext(model.getStructuredDocument(), -1); |
| final ITaglibContextResolver taglibResolver = |
| IStructuredDocumentContextResolverFactory.INSTANCE.getTaglibContextResolver(context); |
| IDOMDocument document = model.getDocument(); |
| getApplicationMap().clear(); |
| getRequestMap().clear(); |
| getSessionMap().clear(); |
| //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, 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(); |
| 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 |
| { |
| IComponentSymbol componentSymbol = |
| SymbolFactory.eINSTANCE.createIComponentSymbol(); |
| componentSymbol.setName(symbolName); |
| |
| updateMap(componentSymbol, aggregator.getScope()); |
| } |
| } |
| } |
| |
| @SuppressWarnings("deprecation") //$NON-NLS-1$ |
| 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" |
| * @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(String scopeName) |
| { |
| final Map<Object, ISymbol> map = getMapForScopeInternal(scopeName); |
| |
| if (map != null) |
| { |
| return Collections.unmodifiableMap(map); |
| } |
| |
| return Collections.EMPTY_MAP; |
| } |
| |
| private void updateMap(ISymbol symbol, 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(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(); |
| } |
| |
| 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; |
| } |
| |
| /** |
| * Aggregates the sets-locale meta-data |
| * |
| * @author cbateman |
| */ |
| private static class LocaleSetAggregator |
| { |
| private final static String SETS_LOCALE = "sets-locale"; //$NON-NLS-1$ |
| |
| static LocaleSetAggregator create(IProject project, |
| final String uri, |
| final String elementName, final String attributeName) |
| { |
| final ITaglibDomainMetaDataModelContext mdContext = TaglibDomainMetaDataQueryHelper.createMetaDataModelContext(project, uri); |
| 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 |
| */ |
| 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); |
| |
| 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 static class CountingMutex extends Object |
| { |
| private boolean _signalled = false; |
| |
| /** |
| * @return true if the state of mutex is signalled |
| */ |
| public synchronized boolean isSignalled() { |
| return _signalled; |
| } |
| |
| /** |
| * @param signalled |
| */ |
| public synchronized void setSignalled(boolean signalled) { |
| this._signalled = signalled; |
| } |
| } |
| } |