/******************************************************************************* * Copyright (c) 2008 SAP * see https://research.qkal.sap.corp/mediawiki/index.php/CoMONET * * Date: $Date: 2009-09-18 14:13:44 +0200 (Fr, 18 Sep 2009) $ * @version $Revision: 7886 $ * @author: $Author: c5106462 $ *******************************************************************************/ package com.sap.furcas.parsergenerator.tcs.t2m.grammar; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.eclipse.emf.ecore.EStructuralFeature; import com.sap.furcas.metamodel.FURCAS.TCS.AndExp; import com.sap.furcas.metamodel.FURCAS.TCS.AtomExp; import com.sap.furcas.metamodel.FURCAS.TCS.BooleanPropertyExp; import com.sap.furcas.metamodel.FURCAS.TCS.ConditionalElement; import com.sap.furcas.metamodel.FURCAS.TCS.EnumLiteralVal; import com.sap.furcas.metamodel.FURCAS.TCS.EqualsExp; import com.sap.furcas.metamodel.FURCAS.TCS.InstanceOfExp; import com.sap.furcas.metamodel.FURCAS.TCS.IntegerVal; import com.sap.furcas.metamodel.FURCAS.TCS.IsDefinedExp; import com.sap.furcas.metamodel.FURCAS.TCS.NegativeIntegerVal; import com.sap.furcas.metamodel.FURCAS.TCS.OneExp; import com.sap.furcas.metamodel.FURCAS.TCS.PropertyReference; import com.sap.furcas.metamodel.FURCAS.TCS.QualifiedNamedElement; import com.sap.furcas.metamodel.FURCAS.TCS.Sequence; import com.sap.furcas.metamodel.FURCAS.TCS.StringVal; import com.sap.furcas.metamodel.FURCAS.TCS.Template; import com.sap.furcas.metamodel.FURCAS.TCS.Value; import com.sap.furcas.parsergenerator.tcs.t2m.grammar.constraints.PropertyInstanceOfConstraint; import com.sap.furcas.parsergenerator.tcs.t2m.grammar.constraints.PropertyQuantityConstraint; import com.sap.furcas.parsergenerator.tcs.t2m.grammar.constraints.RuleBodyPropertyConstraint; import com.sap.furcas.runtime.common.exceptions.MetaModelLookupException; import com.sap.furcas.runtime.common.exceptions.NameResolutionFailedException; import com.sap.furcas.runtime.common.exceptions.SyntaxElementException; import com.sap.furcas.runtime.common.interfaces.IMetaModelLookup; import com.sap.furcas.runtime.common.interfaces.ResolvedNameAndReferenceBean; import com.sap.furcas.runtime.common.util.MessageUtil; import com.sap.furcas.runtime.parser.exceptions.SyntaxParsingException; import com.sap.furcas.runtime.tcs.MetaModelElementResolutionHelper; import com.sap.furcas.runtime.tcs.SyntaxLookup; import com.sap.furcas.runtime.tcs.TemplateNamingHelper; /** * The Class ConditionalElementHandler. */ public class ConditionalElementHandler<Type extends Object> { private IMetaModelLookup<Type> metaLookup; private SyntaxLookup syntaxLookup; private TemplateNamingHelper<Type> namingHelper; private SemanticErrorBucket errorBucket; private MetaModelElementResolutionHelper<Type> resolutionHelper; /** * Instantiates a new conditional element handler. * * @param metaLookup * @param syntaxLookup * @param namingHelper * @param resHelper */ protected ConditionalElementHandler(IMetaModelLookup<Type> metaLookup, SyntaxLookup syntaxLookup, TemplateNamingHelper<Type> namingHelper, MetaModelElementResolutionHelper<Type> resHelper, SemanticErrorBucket errorBucket) { super(); this.metaLookup = metaLookup; this.syntaxLookup = syntaxLookup; this.namingHelper = namingHelper; this.resolutionHelper = resHelper; this.errorBucket = errorBucket; } /** * @param handlerConfig */ protected ConditionalElementHandler(SyntaxElementHandlerConfigurationBean<Type> handlerConfig) { this(handlerConfig.getMetaLookup(), handlerConfig.getSyntaxLookup(), handlerConfig.getNamingHelper(), handlerConfig .getResolutionHelper(), handlerConfig.getErrorBucket()); } /** * Adds a rule body element for the conditional element. This method is a * bit tricky, because it needs to reason what we can set in the model * should we parse a dsl string. I.e. if the Syntax defines (x=3 ? "three" * ), then when we parse "three", we should set x = 3. Also, multiplicity is * a special problem, as if a property is multi valued, within (one books ? * "My" "Book:" books ) the grammar should enforce that the sequence * "My Book book" really contains only one such book. Same for isDefined. * This causes the need to communicate with the propertyHandler in the * context of a ruleBody. * * @param element * the element * @param buffer * the buffer * * @throws SyntaxParsingException * the syntax parsing exception * @throws MetaModelLookupException * the meta model lookup exception */ public void addElement(ConditionalElement element, RuleBodyStringBuffer buffer) throws SyntaxElementException, MetaModelLookupException { if (element.getCondition() == null) { throw new IllegalArgumentException("Condition Element was null for " + element); } QualifiedNamedElement propertyOwnerTypeTemplate = syntaxLookup.getEnclosingQualifiedElement(element); ResolvedNameAndReferenceBean<Type> metaElementRef; try { metaElementRef = resolutionHelper.resolve(propertyOwnerTypeTemplate); } catch (NameResolutionFailedException e) { throw new SyntaxElementException(e.getMessage(), element, e); } if (metaElementRef == null) { throw new SyntaxElementException("Metamodel Element for template " + propertyOwnerTypeTemplate + " could not be resolved.", element); } // at the time of writing, TCS does not define other instances of // Expression than AndExp, // and the code would need change if there ever was a new // implementation, so ClassCastException is okay AndExp expression = (AndExp) element.getCondition(); if (expression.getExpressions() == null) { throw new IllegalArgumentException("Condition Element has null expressions: " + element); } buffer.append(" ("); // need to check the conditions to see whether they contain any // IsDefinedExp, InstanceOf or OneExp, since those // will influence assumptions about properties in embedded syntax // elements (E.g. within OneExp, we assume a // multi-property attribute will only have one element) Collection<AtomExp> conditions = expression.getExpressions(); List<RuleBodyPropertyConstraint> myAddedThenConstraints = new ArrayList<RuleBodyPropertyConstraint>(); for (AtomExp atomExp : conditions) { // first validate the property actually exists in Metamodel String propName = getPropertyName(atomExp); if (metaLookup.getMultiplicity(metaElementRef, propName) == null) { // means feature used in condition does not actually exist in // metamodel errorBucket.addError("Feature with name " + propName + " does not exist for metamodel type " + MessageUtil.asModelName(metaElementRef.getNames()), atomExp); } if (atomExp instanceof IsDefinedExp) { // IsDefinedExp isDefined = (IsDefinedExp) atomExp; PropertyQuantityConstraint constraint = new PropertyQuantityConstraint(propName, PropertyQuantityConstraint.ISDEFINED_KEY, true); buffer.setPropertyConstraint(constraint); myAddedThenConstraints.add(constraint); } else if (atomExp instanceof OneExp) { // OneExp oneExp = (OneExp) atomExp; PropertyQuantityConstraint constraint = new PropertyQuantityConstraint(propName, PropertyQuantityConstraint.ONE_KEY, true); buffer.setPropertyConstraint(constraint); myAddedThenConstraints.add(constraint); } else if (atomExp instanceof InstanceOfExp) { InstanceOfExp instExp = (InstanceOfExp) atomExp; PropertyInstanceOfConstraint constraint = new PropertyInstanceOfConstraint( propName, instExp.getSupertype()); buffer.setPropertyConstraint(constraint); myAddedThenConstraints.add(constraint); } // TODO: validate these constraints against existing constraints and // the metamodel (e.g. one(xyz) AND NOT one(xyz)) } Sequence thenSeq = element.getThenSequence(); // For the one Exp, need to consider here that the multiplicity of // the argument when used in the then part shall be one, (and in the // else part >=2?) // also for isDefined the property should be present in the then part // and absent in the else part // PropertyHandler to use the constratints set previoulsy ObservationDirectivesHelper.appendEnterAlternativeNotification(buffer, 0); buffer.addToRuleFragment(thenSeq); // remove the constraints that were set, and later for the else bit add // their inverse instead Collections.reverse(myAddedThenConstraints); for (Iterator<RuleBodyPropertyConstraint> iterator = myAddedThenConstraints.iterator(); iterator.hasNext();) { RuleBodyPropertyConstraint propertyConstraint = iterator.next(); buffer.removeConstraint(propertyConstraint); } // apend the directives relevant for model injection appendThenPartForInjector(expression, buffer); ObservationDirectivesHelper.appendExitAlternativeNotification(buffer); // now use inverse reasoning to find out what to set in model if this // then part has been parsed // parsed Expression might be (isDefined(p1) and p(2)==5 and isTrue(3)) // then we would have to set something in the model for each "and part". // And later in the else for most of the invert cases as well. // Always append some alternative, even if the alternative is // parseNothing, doNothing, then this creates (...| ), which is // equivalent to ()? // Except for the InstanceOf-Element with empty else part if (!(((AndExp) element.getCondition()).getExpressions().size() > 0 && ((AndExp) element.getCondition()).getExpressions().iterator().next() instanceof InstanceOfExp && element .getElseSequence() == null)) { buffer.append(" | "); ObservationDirectivesHelper.appendEnterAlternativeNotification(buffer, 1); Sequence elseSeq = element.getElseSequence(); if (elseSeq != null) { Collections.reverse(myAddedThenConstraints); List<RuleBodyPropertyConstraint> myAddedElseConstraints = new ArrayList<RuleBodyPropertyConstraint>(); for (Iterator<RuleBodyPropertyConstraint> iterator = myAddedThenConstraints.iterator(); iterator.hasNext();) { RuleBodyPropertyConstraint propertyConstraint = iterator.next(); RuleBodyPropertyConstraint inverseConstraint = propertyConstraint.getInverseConstraint(); if (inverseConstraint != null) { buffer.setPropertyConstraint(inverseConstraint); myAddedElseConstraints.add(inverseConstraint); } } buffer.addToRuleFragment(elseSeq); Collections.reverse(myAddedElseConstraints); for (Iterator<RuleBodyPropertyConstraint> iterator = myAddedElseConstraints.iterator(); iterator.hasNext();) { RuleBodyPropertyConstraint propertyConstraint = iterator.next(); buffer.removeConstraint(propertyConstraint); } } appendElsePartForInjector(expression, buffer); ObservationDirectivesHelper.appendExitAlternativeNotification(buffer); } buffer.append(')'); buffer.append('\n'); } /** * call for iterating expressions. For those where we can know something * about the model from the condition being true, we set what we know in the * model. For those that do not help us, we don't care. * * @param buffer * the buffer * @param andExpression * the and expression * @throws SyntaxParsingException * @throws SyntaxElementException */ private void appendThenPartForInjector(AndExp andExpression, RuleBodyStringBuffer buffer) throws SyntaxElementException { if (andExpression == null || andExpression.getExpressions() == null || andExpression.getExpressions().size() == 0) { throw new IllegalArgumentException("Condition expression was null or empty " + andExpression); } Collection<AtomExp> exprList = andExpression.getExpressions(); List<AtomExp> addToList = new ArrayList<AtomExp>(exprList.size()); // add relevant conditions to list, only do something if resulting list // is not empty for (AtomExp atomExp : exprList) { if ((atomExp instanceof BooleanPropertyExp) || (atomExp instanceof EqualsExp)) { addToList.add(atomExp); } else if (atomExp instanceof IsDefinedExp || atomExp instanceof OneExp) { // don't care, as we cannot define without knowing value } else if (atomExp instanceof InstanceOfExp) { // don't care, as we cannot define without knowing value } else { // should never be thrown, unless TCS syntax metamodel changes throw new RuntimeException("Unknown AtomExp type: " + atomExp.getClass().getName()); } } if (addToList.size() == 0) { return; } buffer.append('{'); for (AtomExp atomExp2 : addToList) { String propName = getPropertyName(atomExp2); if (atomExp2 instanceof BooleanPropertyExp) { buffer.append("setProperty(ret, \"", propName, "\", java.lang.Boolean.TRUE);"); } else if (atomExp2 instanceof EqualsExp) { EqualsExp eqE = (EqualsExp) atomExp2; // i.e. set(ret, "name", new String("John")); buffer.append("setProperty(ret, \"", propName, "\","); Value value = eqE.getValue(); if (value instanceof EnumLiteralVal) { EnumLiteralVal val = (EnumLiteralVal) value; Template owningTemplate = syntaxLookup.getEnclosingQualifiedElement(eqE); // need to find property, then PropertyType, as this would // be the enum try { ResolvedNameAndReferenceBean<Type> metaTypeRef = resolutionHelper.resolve(owningTemplate); ResolvedNameAndReferenceBean<Type> enumName = metaLookup.getFeatureClassReference(metaTypeRef, propName); if (enumName != null) { String enumNameParamString = namingHelper.getMetaTypeListParameter(enumName); buffer.append(" createEnumLiteral(", enumNameParamString, ",\"", val.getName(), "\")"); } else { throw new SyntaxElementException("Type has no such Property: " + owningTemplate + "." + propName, eqE); } } catch (MetaModelLookupException e) { throw new SyntaxElementException("Equals Condition generating failed: " + e.getMessage(), eqE, e); } catch (NameResolutionFailedException e) { throw new SyntaxElementException("Equals Condition generating failed: " + e.getMessage(), eqE, e); } } else { appendValueCreationCommand(buffer, value); } buffer.append(");"); } } buffer.append('}'); } /** * creates a declaration for the property to set (i.e. new String, or maybe * something else like create()?). In case the DSL sample matched. I.e. If * syntax says (value = "test" ? "A" : "B" ) Then in case of "A", the * property "value" should be set to "test". * * Need to TypeCheck the Value to decide what kind of Object to create in * target model. * * @param buffer * @param propertyName * @param value */ private void appendValueCreationCommand(RuleBodyStringBuffer buffer, Value value) { // i.e.: new String("John") if (value == null) { throw new IllegalArgumentException("EqualsExpression had null comparison value"); } String valueClassName;// = value.getClass().getName(); String valueInitParameter;// = value.toString(); if (value instanceof StringVal) { valueClassName = "String"; // just use the String instead of "new String()"? StringVal val = (StringVal) value; valueInitParameter = '\"' + val.getSymbol() + '\"'; } else if (value instanceof IntegerVal) { valueClassName = "Integer"; IntegerVal val = (IntegerVal) value; valueInitParameter = Integer.toString(val.getSymbol()); } else if (value instanceof NegativeIntegerVal) { valueClassName = "Integer"; NegativeIntegerVal val = (NegativeIntegerVal) value; valueInitParameter = "-" + Integer.toString(val.getSymbol()); } else { throw new RuntimeException("Unknown subclass of Value: " + value.getClass()); } buffer.append(" new ", valueClassName, '(', valueInitParameter, ")"); } /** * call for iterating expressions. For those where we can know something * about the model from the condition being FALSE, we set what we know in * the model. * * @param buffer * the buffer * @param andExpression * the and expression */ private void appendElsePartForInjector(AndExp andExpression, RuleBodyStringBuffer buffer) throws SyntaxElementException { if (andExpression == null || andExpression.getExpressions() == null) { return; } // TODO[1] check if right-hand side contains a feature assignment for the feature used in the equality comparison; // in that case it's ok, and no separate feature assignment needs to be generated here Collection<AtomExp> exprList = andExpression.getExpressions(); List<AtomExp> addToList = new ArrayList<AtomExp>(exprList.size()); for (AtomExp atomExp : exprList) { if ((atomExp instanceof BooleanPropertyExp) || (atomExp instanceof EqualsExp)) { addToList.add(atomExp); } else if ((atomExp instanceof IsDefinedExp) || (atomExp instanceof EqualsExp || atomExp instanceof OneExp)) { // don't care, as we cannot define without knowing value } else if (atomExp instanceof InstanceOfExp) { // don't care, as we cannot define without knowing value } else { // should never be thrown, unless TCS syntax metamodel changes throw new RuntimeException("Unknown AtomExp type: " + atomExp.getClass().getName()); } } if (addToList.size() == 0) { return; } buffer.append('{'); for (AtomExp atomExp2 : addToList) { String propName = getPropertyName(atomExp2); if (atomExp2 instanceof BooleanPropertyExp) { buffer.append("setProperty(ret, \"", propName, "\", java.lang.Boolean.FALSE);"); } else if (atomExp2 instanceof EqualsExp) { EqualsExp eqE = (EqualsExp) atomExp2; Value value = eqE.getValue(); if (value instanceof EnumLiteralVal) { EnumLiteralVal val = (EnumLiteralVal) value; Template owningTemplate = syntaxLookup.getEnclosingQualifiedElement(eqE); // need to find property, then PropertyType, as this would // be the enum try { ResolvedNameAndReferenceBean<Type> metaTypeRef = resolutionHelper.resolve(owningTemplate); ResolvedNameAndReferenceBean<Type> enumName = metaLookup.getFeatureClassReference(metaTypeRef, propName); if (enumName != null) { List<String> enumLiterals = metaLookup.getEnumLiterals(enumName); String enumNameParamString = namingHelper.getMetaTypeListParameter(enumName); if (enumLiterals.size() != 2) { throw new SyntaxElementException("Don't know how to handle conditional on Enum "+enumNameParamString+" with more than two literals", eqE); } String otherEnumValue = getOtherEnumLiteral(enumLiterals, val.getName()); buffer.append("setProperty(ret, \"", propName, "\","); buffer.append(" createEnumLiteral(", enumNameParamString, ",\"", otherEnumValue, "\")"); buffer.append(");"); } else { throw new SyntaxElementException("Type has no such Property: " + owningTemplate + "." + propName, eqE); } } catch (MetaModelLookupException e) { throw new SyntaxElementException("Equals Condition generating failed: " + e.getMessage(), eqE, e); } catch (NameResolutionFailedException e) { throw new SyntaxElementException("Equals Condition generating failed: " + e.getMessage(), eqE, e); } //if TODO[1] is done we can re-add this here // } else if (value instanceof IntegerVal || value instanceof NegativeIntegerVal) { // buffer.append("setProperty(ret, \"", propName, "\","); // buffer.append("0"); // buffer.append(");"); // } else if (value instanceof StringVal) { // buffer.append("setProperty(ret, \"", propName, "\","); // buffer.append("null"); // buffer.append(");"); } else { // throw new SyntaxElementException("Don't know how to handle conditional with non-Boolean and non-Enum feature", eqE); } } } buffer.append('}'); } /** * Given a list of enumeration literals with exactly two elements and one * literal assumed to be contained in that list, determines the other * literal. */ private String getOtherEnumLiteral(List<String> enumLiterals, String oneLiteral) { assert enumLiterals.size() == 2; assert enumLiterals.contains(oneLiteral); return enumLiterals.get(1-enumLiterals.indexOf(oneLiteral)); } /** * @param atomExp * @return */ private static String getPropertyName(AtomExp atomExp) { PropertyReference propRef = atomExp.getPropertyReference(); if (propRef != null) { if (propRef.getName() != null) { return propRef.getName(); } else { EStructuralFeature strucFeat = propRef.getStrucfeature(); if (strucFeat != null) { return strucFeat.getName(); } } } return null; } }