package com.sap.furcas.runtime.parser.impl;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import com.sap.furcas.runtime.common.exceptions.ModelAdapterException;
import com.sap.furcas.runtime.common.exceptions.ReferenceSettingException;
import com.sap.furcas.runtime.common.interfaces.IModelElementProxy;
import com.sap.furcas.runtime.parser.ANTLR3LocationToken;
import com.sap.furcas.runtime.parser.IModelAdapter;
import com.sap.furcas.runtime.parser.ModelElementCreationException;
import com.sap.furcas.runtime.parser.ParsingError;
import com.sap.furcas.runtime.parser.TextLocation;
import com.sap.furcas.runtime.parser.impl.context.AmbiguousLookupException;
import com.sap.furcas.runtime.parser.impl.context.ContextManager;
public class ContextLookupDelayedReference extends DelayedReference {
public ContextLookupDelayedReference(IModelElementProxy currentContextElement, Object currentForeachElement,
Object modelElement, String propertyName, List<String> valueTypeName, String keyName, Object keyValue,
String lookIn, String autoCreate, List<String> createAs, boolean importContext, String createIn, boolean b,
ANTLR3LocationToken lastToken) {
super(currentContextElement, currentForeachElement, modelElement, propertyName, valueTypeName, keyName,
keyValue, lookIn, autoCreate, createAs, importContext, createIn, b, ReferenceType.CONTEXT_LOOKUP, lastToken);
}
public ContextLookupDelayedReference(IModelElementProxy currentContextElement, Object currentForeachElement,
Object object, String propertyName, String keyName, Object keyValue, String query, boolean optional,
ANTLR3LocationToken lastToken) {
super(currentContextElement, currentForeachElement, object, propertyName, keyName, keyValue, query, ReferenceType.CONTEXT_LOOKUP,
optional, lastToken);
}
/**
* default way of setting references, instead of using MQL query
*
* @param reference
* @param modelAdapter
* @param referenceContext
* @param contextByElement
* @throws ModelAdapterException
* @throws ModelElementCreationException
*/
public boolean setDelayedReference(DelayedReference reference, IModelAdapter modelAdapter,
ContextManager contextManager, ObservableInjectingParser parser) throws ModelAdapterException,
ModelElementCreationException {
Object referenceContext = reference.getContextElement();
if (referenceContext instanceof IModelElementProxy) {
IModelElementProxy proxyContext = (IModelElementProxy) referenceContext;
referenceContext = proxyContext.getRealObject();
}
boolean referenceSuccessfullySet = false;
boolean problemReported = false;
if ("always".equals(reference.getAutoCreate())) {
create(reference, modelAdapter, contextManager, parser, referenceContext); // may throw exception
referenceSuccessfullySet = true;
} else { // autocreate = "ifMissing" or "never"
try {
if ("#all".equals(reference.getLookIn())) {
Object val = setReferenceUsingModelAdapter(reference, modelAdapter);
if (val != null) {
reference.setRealValue(val);
referenceSuccessfullySet = true;
}
} else if ((reference.getLookIn() != null)) { // lookIn is
// neither #all
// nor null
try {
String[] path = reference.getLookIn().split("\\.");
// navigate to an object, to later use that objects
// context as lookup context
Object navigatedObject = DelayedReferencesHelper.navigateLookIn(referenceContext, reference.getModelElement(), path,
true, modelAdapter, reference.getToken(), contextManager);
Object navigatedContext = contextManager.getContextForElement(navigatedObject);
if (contextManager.hasInTextContext(navigatedContext)) {
// context object is represented in contextManager,
// as it has a representation in the document
referenceSuccessfullySet = setReferenceInContext(reference, modelAdapter, navigatedContext,
contextManager);
}
if (referenceSuccessfullySet == false) {
Object sourceElement = reference.getModelElement();
// we found an element, but this element is not in
// the context of the parsed file
// this means only the modelAdapter may be able to
// set the reference
Object result = modelAdapter.setReferenceWithContextLookup(sourceElement,
reference.getPropertyName(), reference.getValueTypeName(), reference.getKeyName(),
reference.getKeyValue(), navigatedObject);
if (result != null) {
if (!(result instanceof Collection<?>) || (((Collection<?>) result).size() == 0)) {
reference.setRealValue(result);
referenceSuccessfullySet = true;
} else {
problemReported = true;
parser.getInjector().addError(new ParsingError("No instance of " + asModelName(reference.getValueTypeName())
+ " in context path " + reference.getLookIn() + "=" + navigatedObject
+ " with '" + reference.getKeyName() + "' = '" + reference.getKeyValue()
+ "'", reference.getToken()));
}
} else {
parser.getInjector().addError(new ParsingError("No instance of " + asModelName(reference.getValueTypeName())
+ " in context path " + reference.getLookIn() + "=" + navigatedObject
+ " with '" + reference.getKeyName() + "' = '" + reference.getKeyValue() + "'",
reference.getToken()));
}
}
} catch (LookupPathNavigationException e) {
problemReported = true;
parser.getInjector().addError(new ParsingError(e.getMessage(), e.getToken()));
}
} else { // lookIn is null
// try it for current context and all super contexts thereof
Object contextElement = referenceContext;
referenceSuccessfullySet = setReferenceInContext(reference, modelAdapter, contextElement,
contextManager);
}
} catch (AmbiguousLookupException e) {
problemReported = true;
parser.getInjector().addError(new ParsingError("Found several instances suitable as reference: " + reference /*
* + ":" + e.getOriginal() +
* " and " +
* e.getDuplicate() +
* " in context of " +
* e.getContext()
*/, reference.getToken()));
}
} // end if autoCreate = ifmissing or never
if (!referenceSuccessfullySet) {
if (!problemReported) {
if (!"never".equals(reference.getAutoCreate())) {
create(reference, modelAdapter, contextManager, parser, referenceContext);
referenceSuccessfullySet = true;
} else {
Object result = setReferenceUsingModelAdapter(reference, modelAdapter);
if (result == null) {
String message = "Referenced " + asModelName(reference.getValueTypeName()) + " with '"
+ reference.getKeyName() + "' = '" + reference.getKeyValue()
+ "' was not found for property '" + reference.getPropertyName() + "' of "
+ reference.getModelElement().getClass().getName() + " with key value: "
+ reference.getKeyValue();
;
parser.getInjector().addError(new ParsingError(message, reference.getToken()));
referenceSuccessfullySet = false;
} else {
referenceSuccessfullySet = true;
}
}
}
}
// real value might have been set during the cause of setting the
// reference (Same as success?)
if (reference.getRealValue() != null) {
if (reference.isImportContext()) {
contextManager.setContextImport(reference.getModelElement(), reference.getRealValue());
}
}
return referenceSuccessfullySet;
}
/**
* @param reference
* @param modelAdapter
* @return
* @throws ModelAdapterException
* @throws ReferenceSettingException
*/
private Object setReferenceUsingModelAdapter(DelayedReference reference, IModelAdapter modelAdapter)
throws ModelAdapterException, ReferenceSettingException {
// attempt to let adapter resolve reference outside parsing context
Object result = modelAdapter.setReferenceWithLookup(reference.getModelElement(), reference.getPropertyName(),
reference.getValueTypeName(), reference.getKeyName(), reference.getKeyValue());
return result;
}
/**
* check elements within context for one element that could be the right referred element (correct type, keyfield =
* keyvalue).
*
* @param reference
* the reference
* @param modelAdapter
* the model handler
* @param contextElement
* the context
* @param contextManager
*
* @return true, if do it for context
*
* @throws ModelAdapterException
* the model handler exception
* @throws AmbiguousLookupException
*/
private boolean setReferenceInContext(DelayedReference reference, IModelAdapter modelAdapter,
Object contextElement, ContextManager contextManager) throws ModelAdapterException,
AmbiguousLookupException {
// System.out.println("Setting delayed reference " + reference);
// Candidate for being set as referred object
List<String> valueTypeName = reference.getValueTypeName();
Object keyValue = reference.getKeyValue();
String keyName = reference.getKeyName();
Object candidate = null;
candidate = contextManager.findCandidatesInContext(modelAdapter, contextElement, valueTypeName, keyName,
keyValue);
if (candidate != null) {
reference.setRealValue(candidate);
modelAdapter.set(reference.getModelElement(), reference.getPropertyName(), candidate);
return true;
} else {
// recursion upwards, try parent context
// Context parentContext = context.parent();
if (hasCyclicContextParents(contextManager, contextElement)) {
throw new RuntimeException("For some reason " + contextElement
+ " has a cycle in its parent context hierarchy");
}
Object parentContext = contextManager.getContextParent(contextElement);
if (parentContext != null) {
return setReferenceInContext(reference, modelAdapter, parentContext, contextManager);
}
}
return false;
}
/**
* Creates the referred element, sets the key value, and sets it as reference target for the original reference
*
* @param reference
* the reference
* @param modelAdapter
* the model handler
* @param contextManager
* @param referenceContext
* @throws ModelAdapterException
* the model handler exception
* @throws ModelElementCreationException
*/
private void create(DelayedReference reference, IModelAdapter modelAdapter, ContextManager contextManager,
ObservableInjectingParser parser, Object referenceContext) throws ModelAdapterException, ModelElementCreationException {
// create
Object ro = null;
if (reference.getCreateAs() != null) {
ro = ((ModelInjector) parser.getInjector()).doCreate(reference.getCreateAs(), reference.getKeyName(), reference.getKeyValue());
} else {
ro = ((ModelInjector) parser.getInjector()).doCreate(reference.getValueTypeName(), reference.getKeyName(), reference.getKeyValue());
}
reference.setRealValue(ro);
// set reference
Object modelElement = reference.getModelElement();
modelAdapter.set(modelElement, reference.getPropertyName(), ro);
try {
TextLocation location = new TextLocation(reference.getToken());
parser.getInjector().setLocation(ro, location);
} catch (Exception e) {
}
if (reference.getCreateIn() != null) {
try {
String[] path = reference.getCreateIn().split("\\.");
Object containingObject = DelayedReferencesHelper.navigateLookIn(referenceContext, reference.getModelElement(), path, false,
modelAdapter, reference.getToken(), contextManager);
modelAdapter.set(containingObject, path[path.length - 1], ro);
contextManager.addToContext(referenceContext, ro);
} catch (LookupPathNavigationException e) {
parser.getInjector().addError(new ParsingError("Path could not be resolved " + reference.getCreateIn() + " : " + e.getMessage(),
reference.getToken()));
}
}
else if ((reference.getLookIn() != null) && !reference.getLookIn().equals("#all")) {
try {
String[] path = reference.getLookIn().split("\\.");
Object e = DelayedReferencesHelper.navigateLookIn(referenceContext, reference.getModelElement(), path, false, modelAdapter,
reference.getToken(), contextManager);
modelAdapter.set(e, path[path.length - 1], ro);
contextManager.addToContext(referenceContext, ro);
} catch (LookupPathNavigationException e) {
// parser.getInjector().addError(new ParsingError(e.getMessage(), e.getToken());
parser.getInjector().addError(new ParsingError("Path could not be resolved " + reference.getLookIn() + " : " + e.getMessage(),
reference.getToken()));
}
}
}
private static String asModelName(List<String> names) {
if (names == null) {
return null;
}
StringBuilder builder = new StringBuilder();
for (Iterator<String> iterator = names.iterator(); iterator.hasNext();) {
String name = iterator.next();
builder.append(name);
if (iterator.hasNext()) {
builder.append("::");
}
}
return builder.toString();
}
private boolean hasCyclicContextParents(ContextManager contextManager, Object contextElement) {
Set<Object> parents = new HashSet<Object>();
Set<Object> newParents = new HashSet<Object>();
newParents.add(contextElement);
while (newParents.size() > 0) {
Set<Object> newParentsParents = new HashSet<Object>();
for (Object parent : newParents) {
if (parents.contains(parent)) {
return true;
} else {
parents.add(parent);
Object newParent = contextManager.getContextParent(parent);
if (newParent != null) {
newParentsParents.add(newParent);
}
}
}
newParents = newParentsParents;
}
return false;
}
}