package com.sap.furcas.runtime.referenceresolving;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.antlr.runtime.Lexer;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EPackage.Registry;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.ParserException;
import org.eclipse.ocl.ecore.opposites.OppositeEndFinder;
import org.eclipse.ocl.examples.eventmanager.EventManager;
import org.eclipse.ocl.examples.eventmanager.EventManagerFactory;
import com.sap.emf.ocl.trigger.OCLBasedModelUpdater;
import com.sap.emf.ocl.trigger.TriggerManager;
import com.sap.emf.ocl.trigger.TriggerManagerFactory;
import com.sap.furcas.metamodel.FURCAS.TCS.ClassTemplate;
import com.sap.furcas.metamodel.FURCAS.TCS.ConcreteSyntax;
import com.sap.furcas.metamodel.FURCAS.TCS.ForeachPredicatePropertyInit;
import com.sap.furcas.metamodel.FURCAS.TCS.InjectorAction;
import com.sap.furcas.metamodel.FURCAS.TCS.LookupPropertyInit;
import com.sap.furcas.metamodel.FURCAS.TCS.Property;
import com.sap.furcas.metamodel.FURCAS.TCS.PropertyInit;
import com.sap.furcas.metamodel.FURCAS.TCS.Template;
import com.sap.furcas.metamodel.FURCAS.textblocks.LexedToken;
import com.sap.furcas.runtime.parser.ParserFactory;
import com.sap.furcas.runtime.parser.impl.ModelUpdaterRegistry;
import com.sap.furcas.runtime.parser.impl.ObservableInjectingParser;
import com.sap.furcas.runtime.tcs.PropertyArgumentUtil;
import com.sap.furcas.runtime.tcs.TcsUtil;
/**
* Manages the relationship between {@link ConcreteSyntax FURCAS syntaxes} with their OCL-based
* {@link OCLBasedModelUpdater model updaters} and the Ecore metamodel packages for which the syntaxes are
* registered. A plugin that contributes a FURCAS syntax and that wants the syntax's model
* updaters to be loaded
*
* TODO Currently, all TriggerManagers share a single EventManager which can cause trouble during individual registering/unregistering. Consider using a single TriggerManager only.
*/
public class SyntaxRegistry implements TokenChanger, ModelUpdaterRegistry {
private final Map<URI, TriggerManager> triggerManagersForSyntax;
/**
* For each {@link Property} registered in either
* {@link #registerPropertiesWithQuery(Collection, TriggerManager, OppositeEndFinder, IProgressMonitor)} or
* {@link #registerInjectorActions(Collection, TriggerManager, ParserFactory, OppositeEndFinder, IProgressMonitor)},
* stores the {@link Property}/{@link PropertyInit}'s {@link URI} and maps it to the model updater responsible for
* keeping the property up-to-date.
*/
private final Map<URI, AbstractFurcasOCLBasedModelUpdater> updatersForPropertiesAndInjectorActions;
/**
* The single event manager to be used by all {@link TriggerManager}s created by this registry.
* This ensures that only one adapter needs to be registered and that all event filters can be
* scalably centralized in one event manager instead of requiring another event manager per
* syntax registered.
*/
private final EventManager eventManager;
private final Map<URI, ParserFactory<? extends ObservableInjectingParser, ? extends Lexer>> parserFactoriesForSyntax;
private final Registry metamodelRegistry;
/**
* Components registered by {@link #addTokenChanger(TokenChanger)} and deregistered by
* {@link #removeTokenValueChanger(TokenChanger)} that get called when, e.g., due to a rename,
* a token shall change its value. However, as life cycle constraints such as read-only
* resources or wide-ranging queries may complicate matters, token updates are not performed
* immediately but are delegates to the components registered here.
*/
private final Set<TokenChanger> tokenChangers;
public SyntaxRegistry() {
triggerManagersForSyntax = new HashMap<URI, TriggerManager>();
parserFactoriesForSyntax = new HashMap<URI, ParserFactory<? extends ObservableInjectingParser, ? extends Lexer>>();
eventManager = EventManagerFactory.eINSTANCE.createEventManager();
metamodelRegistry = Registry.INSTANCE;
tokenChangers = new HashSet<TokenChanger>();
updatersForPropertiesAndInjectorActions = new HashMap<URI, AbstractFurcasOCLBasedModelUpdater>();
}
/**
* After calling this method, all change {@link Notification notifications} coming from the
* <code>registerWith</code> resource set and all its contained elements are analyzed. If they
* trigger any of the OCL-based model updater rules from any of the syntaxes currently loaded,
* the updater fires and updates the model accordingly.
*/
public void registerAllLoadedSyntaxesTriggerManagers(ResourceSet registerWith) {
eventManager.addToObservedResourceSets(registerWith);
}
public void unregisterAllLoadedSyntaxesTriggerManagers(ResourceSet registeredWith) {
eventManager.removeFromObservedResourceSets(registeredWith);
}
/**
* Registers a concrete syntax with all the OCL expressions it contains,
* producing or fetching from a cache a {@link TriggerManager}
*/
public void registerSyntaxForIncrementalEvaluation(ConcreteSyntax syntax, OppositeEndFinder oppositeEndFinder,
IProgressMonitor monitor, ParserFactory<? extends ObservableInjectingParser, ? extends Lexer> parserFactory) {
URI syntaxURI = EcoreUtil.getURI(syntax);
TriggerManager triggerManager = triggerManagersForSyntax.get(syntaxURI);
if (triggerManager == null) {
parserFactoriesForSyntax.put(syntaxURI, parserFactory);
triggerManager = TriggerManagerFactory.INSTANCE.createTriggerManager(oppositeEndFinder, eventManager);
triggerManagersForSyntax.put(syntaxURI, triggerManager);
fillTriggerManagerForSyntax(triggerManager, syntax, parserFactory, oppositeEndFinder, monitor);
}
}
public void unregisterSyntax(ConcreteSyntax syntax) {
URI syntaxURI = EcoreUtil.getURI(syntax);
parserFactoriesForSyntax.remove(syntaxURI);
triggerManagersForSyntax.remove(syntaxURI);
}
private void fillTriggerManagerForSyntax(TriggerManager triggerManager, ConcreteSyntax syntax,
ParserFactory<? extends ObservableInjectingParser, ? extends Lexer> parserFactory,
OppositeEndFinder oppositeEndFinder, IProgressMonitor monitor) {
if (monitor == null) {
monitor = new NullProgressMonitor();
}
// fetch all InjectorAction and Property elements from the syntax
Collection<InjectorAction> injectorActions = getInjectorActions(syntax);
Collection<Property> propertiesWithQuery = getPropertiesWithQuery(syntax);
monitor.beginTask("Registering OCL expressions for Impact Analysis for syntax: " + syntax.getName(),
injectorActions.size()+propertiesWithQuery.size());
try {
registerInjectorActions(injectorActions, triggerManager, parserFactory, oppositeEndFinder, monitor);
registerPropertiesWithQuery(propertiesWithQuery, triggerManager, oppositeEndFinder, monitor);
} catch (ParserException e) {
throw new RuntimeException(e);
} finally {
monitor.done();
}
}
private void registerPropertiesWithQuery(Collection<Property> propertyInits, TriggerManager triggerManager,
OppositeEndFinder oppositeEndFinder, IProgressMonitor monitor) throws ParserException {
monitor.subTask("Property Queries");
for (Property property : propertyInits) {
monitor.worked(1);
Template template = property.getParentTemplate();
if (template != null && template instanceof ClassTemplate && PropertyArgumentUtil.getReferenceByPArg(property) != null) {
OCLQueryPropertyUpdater updater = new OCLQueryPropertyUpdater(property, metamodelRegistry, oppositeEndFinder, this);
triggerManager.register(updater);
updatersForPropertiesAndInjectorActions.put(EcoreUtil.getURI(property), updater);
}
}
}
private void registerInjectorActions(Collection<InjectorAction> injectorActions, TriggerManager triggerManager,
ParserFactory<? extends ObservableInjectingParser, ? extends Lexer> parserFactory,
OppositeEndFinder oppositeEndFinder, IProgressMonitor monitor)
throws ParserException {
monitor.subTask("PropertyInits");
for (InjectorAction injectorAction : injectorActions) {
monitor.worked(1);
AbstractFurcasOCLBasedModelUpdater updater = null;
if (injectorAction instanceof LookupPropertyInit) {
updater = new SimplePropertyInitUpdater((LookupPropertyInit) injectorAction,
metamodelRegistry, oppositeEndFinder);
} else if (injectorAction instanceof ForeachPredicatePropertyInit) {
updater = new ForeachPropertyInitUpdater((ForeachPredicatePropertyInit) injectorAction,
metamodelRegistry, parserFactory, oppositeEndFinder);
}
if (updater != null) {
triggerManager.register(updater);
updatersForPropertiesAndInjectorActions.put(EcoreUtil.getURI(injectorAction), updater);
}
}
}
@Override
public AbstractFurcasOCLBasedModelUpdater getModelUpdater(URI propertyOrInjectorActionURI) {
return updatersForPropertiesAndInjectorActions.get(propertyOrInjectorActionURI);
}
private Collection<Property> getPropertiesWithQuery(ConcreteSyntax syntax) {
Collection<Property> result = TcsUtil.getElementsOfType(syntax, Property.class);
for (Iterator<Property> i = result.iterator(); i.hasNext(); ) {
Property p = i.next();
if (!PropertyArgumentUtil.containsLookupScopePArg(p)) {
i.remove();
}
}
return result;
}
private Collection<InjectorAction> getInjectorActions(ConcreteSyntax syntax) {
return TcsUtil.getElementsOfType(syntax, InjectorAction.class);
}
/**
* If <code>oldTokenValue</code> and <code>newTokenValue</code> differ, dispatches this request to all
* {@link TokenChanger}s that were {@link #addTokenChanger(TokenChanger) registered} with this syntax registry.
*/
@Override
public void requestTokenValueChange(LexedToken token, String oldTokenValue, String newTokenValue) {
if (!oldTokenValue.equals(newTokenValue)) {
for (TokenChanger tokenChanger : tokenChangers) {
tokenChanger.requestTokenValueChange(token, oldTokenValue, newTokenValue);
}
}
}
public void addTokenChanger(TokenChanger tokenChanger) {
tokenChangers.add(tokenChanger);
}
public void removeTokenValueChanger(TokenChanger tokenChanger) {
tokenChangers.remove(tokenChanger);
}
}