package com.sap.furcas.runtime.parser.impl;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import com.sap.furcas.metamodel.FURCAS.TCS.ForeachPredicatePropertyInit;
import com.sap.furcas.metamodel.FURCAS.TCS.Template;
import com.sap.furcas.metamodel.FURCAS.textblocks.ForEachExecution;
import com.sap.furcas.metamodel.FURCAS.textblocks.TextBlock;
import com.sap.furcas.metamodel.FURCAS.textblocks.TextblocksFactory;
import com.sap.furcas.runtime.common.exceptions.ModelAdapterException;
import com.sap.furcas.runtime.common.exceptions.SyntaxElementException;
import com.sap.furcas.runtime.common.implementation.ResolvedModelElementProxy;
import com.sap.furcas.runtime.common.interfaces.IModelElementProxy;
import com.sap.furcas.runtime.common.interfaces.IRuleName;
import com.sap.furcas.runtime.parser.ANTLR3LocationToken;
import com.sap.furcas.runtime.parser.IModelAdapter;
import com.sap.furcas.runtime.parser.IModelInjector;
import com.sap.furcas.runtime.parser.IParsingObserver;
import com.sap.furcas.runtime.parser.ModelElementCreationException;
import com.sap.furcas.runtime.parser.ParsingError;
import com.sap.furcas.runtime.parser.exceptions.UnknownProductionRuleException;
import com.sap.furcas.runtime.parser.impl.context.ContextManager;
import com.sap.furcas.runtime.tcs.TcsUtil;
public class ForeachDelayedReference extends DelayedReference {
/**
* Used by
* {@link ObservableInjectingParser#setPredicateRef(Object, String, String, String, List, IRuleName, boolean, String, ModelUpdaterRegistry)}
*/
public ForeachDelayedReference(Object referenceContextObject,
Object modelElement, String propertyName, String oclQuery,
String mode, List<PredicateSemantic> list,
IRuleName ruleNameFinder, ANTLR3LocationToken token,
boolean hasContext, boolean isOptional) {
super(referenceContextObject, DelayedReference.ReferenceType.TYPE_FOREACH_PREDICATE, modelElement,
propertyName, oclQuery, mode, list, ruleNameFinder, token, hasContext, isOptional);
}
/**
* Sets the delayed reference.
*
* @param reference
* the reference
* @param modelAdapter
* the model handler
* @param contextByElement
* the context by element
* @return <code>true</code> if the reference was resolved successfully, <code>false</code> else.
*
* @throws ModelAdapterException
* the model handler exception
* @throws ModelElementCreationException
*/
@Override
public boolean setDelayedReference(DelayedReference reference, IModelAdapter modelAdapter, ContextManager contextManager,
ObservableInjectingParser parser) throws ModelAdapterException, ModelElementCreationException {
Object contextElement = reference.getContextElement();
if (contextElement instanceof IModelElementProxy) {
IModelElementProxy proxyContext = (IModelElementProxy) contextElement;
contextElement = proxyContext.getRealObject();
}
return setDelayedReferenceWithPredicate(reference, modelAdapter, contextManager, contextElement, parser);
}
/**
* If the <tt>reference</tt>'s {@link DelayedReference#getModelElement() model element} is a proxy, resolve it first.
*/
private void resolveModelElementProxy(DelayedReference reference, IModelAdapter modelAdapter) throws ModelAdapterException,
ModelElementCreationException {
if (reference.getModelElement() instanceof ModelElementProxy) {
ModelElementProxy proxy = (ModelElementProxy) reference.getModelElement();
if (proxy.getRealObject() == null) {
Object result;
result = modelAdapter.createOrResolveElement(proxy.getType(), proxy.getAttributeMap(), null, null, false, true);
if (result instanceof EObject) {
reference.setModelElement(result);
}
} else {
reference.setModelElement(proxy.getRealObject());
}
}
}
private boolean setDelayedReferenceWithPredicate(DelayedReference reference, IModelAdapter modelAdapter,
ContextManager contextManager, Object contextElement, ObservableInjectingParser parser) throws ModelAdapterException {
try {
contextElement = DelayedReferencesHelper.getNavigatedContextElementFromReference(reference, modelAdapter, contextManager, contextElement);
// when the element is a Proxy resolve it first
resolveModelElementProxy(reference, modelAdapter);
if (reference.getOclQuery() == null) {
parser.getInjector().addError(new ParsingError("You must specify an OCL query.", reference.getToken()));
return false;
}
Collection<?> result = DelayedReferencesHelper.evaluateForeachOcl((EObject) reference.getModelElement(), reference, modelAdapter,
contextElement);
List<ForeachProductionResult> producedResults = new ArrayList<ForeachProductionResult>();
// loop over the results to handle them one by one,
// delete all elements that were created by this foreach but are
// not valid anymore
String mode = reference.getMode();
IRuleName ruleNameFinder = reference.getRuleNameFinder();
for (Object singleForeachResult : result) {
if (!(singleForeachResult instanceof Boolean) || ((Boolean) singleForeachResult).booleanValue()) {
// look if there are possible when/as constructs
PredicateSemantic activePredicateSemantic = getActivePredicateFromWhenAsClauses(reference,
modelAdapter, contextElement, singleForeachResult);
Template templateUsedForProduction = getTemplateFromPredicateSemantic(activePredicateSemantic, reference);
// TODO it would be nice to compute the rule name from the template; however, if no textblocks are being produced, the reference doesn't have the link to the ForeachPropertyInit set, so the template can't be determined and isn't passed in by the parser run; only the rule name is passed
String parserRuleNameToUseForProduction = computeRuleName(parser, singleForeachResult, activePredicateSemantic, mode, ruleNameFinder);
if (parserRuleNameToUseForProduction == null) {
throw new UnknownProductionRuleException("At least one as parameter is needed in that case.");
}
ModelElementProxy foreachTargetElement = produceForOneForeachResult(reference, modelAdapter,
contextElement, parser, singleForeachResult, activePredicateSemantic, parserRuleNameToUseForProduction);
ForeachProductionResult resultObject = new ForeachProductionResult(foreachTargetElement, templateUsedForProduction,
singleForeachResult);
producedResults.add(resultObject);
}
}
setReferenceAndUpdateForeachContexts(reference, modelAdapter, parser.getInjector(), producedResults);
} catch (Exception e) {
parser.getInjector().addError(new ParsingError(e.getMessage(), reference.getToken()));
return false;
}
return true;
}
private static class ForeachProductionResult {
private final ModelElementProxy producedProxy;
private final Template templateUsedForProduction;
private final Object foreachExpressionResultForWhichProduced;
public ForeachProductionResult(ModelElementProxy producedProxy, Template templateUsedForProduction,
Object foreachExpressionResultForWhichProduced) {
super();
this.producedProxy = producedProxy;
this.templateUsedForProduction = templateUsedForProduction;
this.foreachExpressionResultForWhichProduced = foreachExpressionResultForWhichProduced;
}
public ModelElementProxy getProducedProxy() {
return producedProxy;
}
public Template getTemplateUsedForProduction() {
return templateUsedForProduction;
}
public Object getForeachExpressionResultForWhichProduced() {
return foreachExpressionResultForWhichProduced;
}
}
/**
* A <code>foreach</code> expression was (re-)evaluated, and for each result, based on any <code>when</code>/
* <code>as</code> {@link com.sap.furcas.metamodel.FURCAS.TCS.PredicateSemantic} elements, the corresponding
* template's production rule was executed by the parser. This produced a {@link ModelElementProxy} which has not
* yet been turned into a real {@link EObject} yet nor has it been entered into the target feature.
* <p>
*
* Prior executions of the same <code>foreach</code> predicate on the same source model element may have happened.
* In this case, there will be {@link ForEachExecution} records documenting this. If no such records are found, all
* proxies produced will be materialized as {@link EObject}s and
* {@link #setReference(DelayedReference, Object, IModelAdapter, ModelElementProxy, IModelInjector, int) added/set} to/into the target
* feature described by the <code>reference</code>.
* <p>
*
* If {@link ForEachExecution} objects are found for the source element and <code>foreach</code> predicate, their
* {@link ForEachExecution#getResultModelElement() result elements} are obtained and compared to the new
* {@link ForeachProductionResult production instructions} which also contain the {@link Template} used to produce
* the target element. The template is used to compare the element types in order to try to re-use already existing
* elements.
* <p>
*
* In case a target object from a prior evaluation can be re-used, it is set as the proxy's
* {@link ModelElementProxy#setRealObject(Object) real object}, and no new target object is created for this
* <code>foreach</code> result. Otherwise, a new target object is created. The re-used or created object is then
* used to replace (single multiplicity) or to add to (many-multiplicity) the target feature.
* <p>
*
* As to the re-use strategy, we distinguish between re-using the {@link ForEachExecution} hull only and the
* {@link ForEachExecution#getResultModelElement() result element} that was produced in an earlier run. Two element
* collections are compared, namely those listed as {@link ForEachExecution#getResultModelElement() result elements}
* of earlier evaluations on the <code>reference</code>'s {@link DelayedReference#getTextBlock() text block} for the
* same <code>foreach</code> predicate; and those described by <code>producedResults</code> which contains proxies
* and the templates telling the types and modes with which they were created. The two collections are iterated in
* parallel. If the {@link ForEachExecution}'s template matches that in the current <code>producedResults</code>
* element, the {@link ForEachExecution#getResultModelElement() old result element} is re-used by
* {@link ModelElementProxy#setRealObject(Object) setting} it on the proxy. Otherwise, a new element is produced
* using {@link ModelInjector#createOrResolve(ModelElementProxy, ANTLR3LocationToken, ANTLR3LocationToken)} and the
* existing {@link ForEachExecution} is updated for the new result element, template, etc. If the collection of
* {@link ForEachExecution} elements is shorter than that of the <code>producedResults</code>, additional
* {@link ForEachExecution} elements are added to the <code>reference</code>'s {@link TextBlock#getForEachExecutions()
* textblock's foreach context list}. Extraneous elements are deleted from it.
* <p>
*
* It's obvious that there may be more sophisticated re-use strategies based on longest sequence etc., but those may
* be added later.
* @param injector TODO
* @param producedResults the {@link ForeachProductionResult#getTemplateUsedForProduction() templates} need to be
* set only if text blocks are being produced by the parser run
*/
private void setReferenceAndUpdateForeachContexts(DelayedReference reference, IModelAdapter modelAdapter,
IModelInjector injector, List<ForeachProductionResult> producedResults)
throws ModelElementCreationException, ModelAdapterException {
Iterator<ForEachExecution> foreachContextIterator = null;
ForEachExecution nextOldForeachContext = null;
if (reference.getTextBlock() != null) {
foreachContextIterator = ((TextBlock) reference.getTextBlock()).getForEachExecutions().iterator();
nextOldForeachContext = getNextForeachContext(foreachContextIterator, reference);
}
int i=0;
for (ForeachProductionResult producedResult : producedResults) {
// check for a re-usable target element of ForEachContext with its result element
if (nextOldForeachContext != null && isTargetElementReusable(nextOldForeachContext, producedResult)) {
// re-use the target object referenced by the ForEachContext and the ForEachContext itself
producedResult.getProducedProxy().setRealObject(nextOldForeachContext.getResultModelElement());
if (producedResult.getForeachExpressionResultForWhichProduced() instanceof EObject) {
nextOldForeachContext.setContextElement((EObject) producedResult.getForeachExpressionResultForWhichProduced());
} else if (producedResult.getForeachExpressionResultForWhichProduced() instanceof EObject) {
nextOldForeachContext.setContextString((String) producedResult.getForeachExpressionResultForWhichProduced());
}
reference.setRealValue(nextOldForeachContext.getResultModelElement());
} else {
// target element is not re-usable, a new one needs to be produced
setReference(reference, producedResult.getForeachExpressionResultForWhichProduced(), modelAdapter,
producedResult.getProducedProxy(), injector, i);
if (reference.getTextBlock() != null && producedResult.getTemplateUsedForProduction() != null) {
// if we have a ForEachContext element for the current source element and foreach predicate,
// re-use and update it:
if (nextOldForeachContext == null) {
// no ForEachContext element; produce a new one and append
ForEachExecution newContext = produceNewForEachContext(reference, producedResult);
((TextBlock) reference.getTextBlock()).getForEachExecutions().add(newContext);
} else {
// ForEachContext names a template/element that can't be re-used; update it correspondingly
Object result = producedResult.getForeachExpressionResultForWhichProduced();
if (result instanceof EObject) {
nextOldForeachContext.setContextElement((EObject) producedResult
.getForeachExpressionResultForWhichProduced());
} else if (result instanceof String) {
nextOldForeachContext.setContextString((String) producedResult
.getForeachExpressionResultForWhichProduced());
}
nextOldForeachContext.setTemplateUsedForProduction(producedResult.getTemplateUsedForProduction());
nextOldForeachContext.setResultModelElement((EObject) producedResult.getProducedProxy()
.getRealObject());
}
}
}
if (nextOldForeachContext != null && foreachContextIterator != null && foreachContextIterator.hasNext()) {
nextOldForeachContext = foreachContextIterator.next();
} else {
nextOldForeachContext = null;
}
i++;
}
// delete remaining old ForEachContext entries from reference's textblock
while (nextOldForeachContext != null) {
foreachContextIterator.remove();
nextOldForeachContext = getNextForeachContext(foreachContextIterator, reference);
}
}
private ForEachExecution produceNewForEachContext(DelayedReference reference, ForeachProductionResult producedResult) {
ForEachExecution newContext = TextblocksFactory.eINSTANCE.createForEachExecution();
newContext.setForeachPedicatePropertyInit((ForeachPredicatePropertyInit) reference.getQueryElement());
newContext.setSourceModelElement((EObject) reference.getModelElement());
newContext.setContextElement((EObject) producedResult.getForeachExpressionResultForWhichProduced());
newContext.setTemplateUsedForProduction(producedResult.getTemplateUsedForProduction());
newContext.setResultModelElement((EObject) reference.getRealValue());
return newContext;
}
/**
* For a {@link ForEachExecution} that is known to be for the same source element and <code>foreach</code>
* clause as <code>producedResult</code>, checks if the template used by the previous execution as
* documented by <code>nextOldForeachContext</code> is the same as that used in <code>producedResult</code>.
*/
private boolean isTargetElementReusable(ForEachExecution nextOldForeachContext, ForeachProductionResult producedResult) {
return nextOldForeachContext.getTemplateUsedForProduction() == producedResult.getTemplateUsedForProduction();
}
/**
* Finds the next {@link ForEachExecution} in the collection iterated by <code>foreachContextIterator</code>
* that is for the same {@link DelayedReference#getQueryElement() property init} as the <code>reference</code>
* and that is for the same {@link ForEachExecution#getSourceModelElement() source element} as the
* <code>reference</code>. If no such element is found, <code>null</code> is returned
*
* Postcondition: if a non-<code>null</code> result is returned, the <code>foreachContextIterator</code>
* is at that element so that calling {@link Iterator#remove()} removes the element just returned
*
* @param foreachContextIterator must not be <code>null</code>
*/
private ForEachExecution getNextForeachContext(Iterator<ForEachExecution> foreachContextIterator,
DelayedReference reference) {
ForEachExecution result = null;
while (foreachContextIterator.hasNext() && result == null) {
ForEachExecution fec = foreachContextIterator.next();
if (fec.getForeachPedicatePropertyInit().equals(reference.getQueryElement())
&& reference.getModelElement().equals(fec.getSourceModelElement())) {
result = fec;
}
}
return result;
}
/**
* Determines the production rule for the <code>singleForeachResult</code> by
* {@link #getActivePredicateFromWhenAsClauses(DelayedReference, IModelAdapter, Object, Object) scanning} for the
* applicable predicate semantic. If found, the parser is used to execute the production rule. The result is
* assigned to the feature as defined by the <code>reference</code> using
* {@link #setReference(ObservableInjectingParser, DelayedReference, Object, String, IModelAdapter, EObject)}. If no
* applicable production rule is found, a {@link UnknownProductionRuleException} is thrown.
* <p>
* @param singleForeachResult
* the current foreach-expression's result to produce an element for
* @param parserRuleName TODO
* @return the element proxy describing the element to produce
*/
private ModelElementProxy produceForOneForeachResult(DelayedReference reference, IModelAdapter modelAdapter,
Object contextElement, ObservableInjectingParser parser,
Object singleForeachResult, PredicateSemantic activePredicateSemantic, String parserRuleName) throws ModelAdapterException,
SyntaxElementException, NoSuchMethodException, UnknownProductionRuleException, IllegalAccessException,
InvocationTargetException, ModelElementCreationException {
ModelElementProxy foreachTargetElement = null;
// look if there are possible when/as constructs
foreachTargetElement = produceSingleForeachUsingParserRule(parser, reference, singleForeachResult, parserRuleName);
return foreachTargetElement;
}
private String computeRuleName(ObservableInjectingParser parser, Object singleForeachResult,
PredicateSemantic activePredicateSemantic, String mode, IRuleName ruleNameFinder)
throws SyntaxElementException {
String ruleName = null;
if (activePredicateSemantic == null) {
// no matching when/as combination; perform default handling:
if (singleForeachResult instanceof EObject) {
EClass foreachElementType = ((EObject) singleForeachResult).eClass();
// get the template
Template tmpl = TcsUtil.findTemplate(foreachElementType, mode, Collections.singletonList(URI.createURI(
parser.getSyntaxUUID())));
// get the rule name from the template
ruleName = ruleNameFinder.getRuleName(tmpl, mode);
} else {
// handle the base types
if (!(singleForeachResult instanceof String) || !(singleForeachResult instanceof Number)) {
throw new IllegalArgumentException("The OCL element " + singleForeachResult
+ " cannot be used, as it is neither of type String nor a Number");
}
}
} else {
ruleName = activePredicateSemantic.getAs();
}
return ruleName;
}
private void setReference(DelayedReference reference, Object singleForeachResult, IModelAdapter modelAdapter,
ModelElementProxy foreachTargetElement, IModelInjector injector, int position) throws ModelElementCreationException,
ModelAdapterException {
// add the parsed part to the object first try to resolve if there is a model element that already
// exists and can be reused
reference.setRealValue(injector.createOrResolve(foreachTargetElement, null, null));
// by default use partition of reference.getModelElement
if (reference.getModelElement() instanceof EObject && reference.getRealValue() instanceof EObject) {
((EObject) reference.getModelElement()).eResource().getContents().add((EObject) reference.getRealValue());
}
modelAdapter.set(reference.getModelElement(), reference.getPropertyName(), reference.getRealValue(), position);
}
private ModelElementProxy produceSingleForeachUsingParserRule(ObservableInjectingParser parser, DelayedReference reference,
Object next, String ruleName) throws NoSuchMethodException, UnknownProductionRuleException,
IllegalAccessException, InvocationTargetException, ModelElementCreationException {
// invoke the parser to execute the template
Method methodToCall = parser.getClass().getMethod(ruleName);
// parser.reset();
if (!Modifier.isFinal(methodToCall.getModifiers())) {
throw new UnknownProductionRuleException(ruleName + " is not a production rule in generated Parser.");
}
boolean originalResolveProxiesValue = parser.isResolveProxies();
parser.setResolveProxies(false);
DelegationParsingObserver delegator = new DelegationParsingObserver();
IParsingObserver originalObserver = parser.getObserver();
if (originalObserver != null) {
delegator.addParsingObserver(originalObserver);
}
delegator.addParsingObserver(new ForeachParsingObserver((TextBlock) reference.getTextBlock()));
parser.setObserver(delegator);
IModelElementProxy proxyForContextElement = null;
if (reference.getContextElement() instanceof IModelElementProxy) {
proxyForContextElement = (IModelElementProxy) reference.getContextElement();
} else {
proxyForContextElement = new ResolvedModelElementProxy(reference.getContextElement());
}
parser.setCurrentForeachElement(next);
if (parser.getContextManager().getContextForElement(reference.getContextElement()) == null) {
parser.addContext(proxyForContextElement);
if (proxyForContextElement.getRealObject() != null && reference.getContextElement() instanceof EObject) {
parser.getContextManager().notifyProxyResolvedWith(proxyForContextElement, reference.getContextElement(),
/*
* no creation context element needs to be provided here because the proxy has just been created and has not been
* added to any other context
*/
null);
}
} else {
parser.getCurrentContextStack().push(proxyForContextElement); // the Context object was already created elsewhere
}
try {
ModelElementProxy parseReturn = (ModelElementProxy) methodToCall.invoke(parser);
if (parseReturn == null) {
throw new ModelElementCreationException("Unable to create model element using parse rule " + ruleName
+ ". Parse errors: " + parser.getInjector().getErrorList());
}
return parseReturn;
} finally {
parser.getCurrentContextStack().pop();
parser.setObserver(originalObserver);
parser.setResolveProxies(originalResolveProxiesValue);
}
}
private PredicateSemantic getActivePredicateFromWhenAsClauses(DelayedReference reference, IModelAdapter modelAdapter,
Object contextElement, Object currentForeachElement) throws ModelAdapterException {
for (PredicateSemantic nextPred : reference.getPredicateActionList()) {
if (nextPred.getWhen() != null) {
Collection<?> resultBool = modelAdapter.evaluateOCLQuery(currentForeachElement,
reference.getKeyValue(), nextPred.getWhen(), contextElement);
if (resultBool.size() == 1) {
Iterator<?> resIt = resultBool.iterator();
Object nextBool = resIt.next();
if (nextBool instanceof Boolean && (Boolean) nextBool) {
return nextPred;
}
}
} else {
return nextPred; // no when-clause means "handle always"
}
}
return null;
}
/**
* Tries to find a {@link DelayedReference#getQueryElement() foreach property init} on the delayed reference <code>ref</code>.
* Assuming that <code>ref</code> is a {@link DelayedReference.ReferenceType#TYPE_FOREACH_PREDICATE} reference,
* its {@link DelayedReference#getPredicateActionList() predicate list} (the list of when/as/mode clauses}
* is used to find the position/index of <code>activePredicateSemantic</code>. At this position,
* the {@link com.sap.furcas.metamodel.FURCAS.TCS.PredicateSemantic#getAs() template} to be used for production
* is looked up.<p>
*
* If either the reference doesn't have the property init set in its {@link DelayedReference#getQueryElement()},
* <code>null</code> is returned. This will in particular be the case if the parser is not configured to produce
* text blocks.
*/
private Template getTemplateFromPredicateSemantic(PredicateSemantic activePredicateSemantic, DelayedReference ref) {
int index = ref.getPredicateActionList().indexOf(activePredicateSemantic);
if (index >= 0 && ((ForeachPredicatePropertyInit) ref.getQueryElement()) != null) {
int i = 0;
for (com.sap.furcas.metamodel.FURCAS.TCS.PredicateSemantic predSem : ((ForeachPredicatePropertyInit) ref
.getQueryElement()).getPredicateSemantic()) {
if (i++ == index) {
return predSem.getAs();
}
}
return null;
} else {
return null;
}
}
}