| /******************************************************************************* |
| * 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(); |
| } |
| } |
| } |
| } |