Examples of Custom Effectivity String Notation

This section provides examples of customizing the Effectivity String Notation. These simple examples supplement the concepts detailed in sections Customizing Effectivity String Notation on Effectivity Expression and Customizing Unified Effectivity String Notation.

Custom Effectivity String Notation on Effectivity Expression

The default Effectivity String Notations Converter uses a conjunction word for each logical operator:

  • AND for logical conjunction
  • OR for logical disjunction
  • NOT for logical complement

For example:

((Factory = Munich AND Production Date >= 01/01/2020) OR

(Factory = Detroit AND Production Date >= 06/01/2020)) AND

NOT (Body Type = Hatchback)

The customized String Notation will use the following characters instead of the conjunction words:

  • && for logical conjunction
  • || for logical disjunction
  • ! for logical complement

For example:

((Factory = Munich && Production Date >= 01/01/2020) ||(Factory = Detroit && Production Date >= 06/01/2020)) && !(Body Type = Hatchback)

ExpressionToShortStringNotationConverter Class Definition

To achieve this customization goal, a new ExpressionToShortStringNotationConverter class is defined representing the new Converter: internal class ExpressionToShortStringNotationConverter : Aras.Server.Core.Configurator.IStringNotationConverter
{
private readonly IEnumerable<Aras.Server.Core.Configurator.Variable> _variablesContainer;

private static readonly Dictionary<Type, Aras.Server.Core.Configurator.DataType> typeTranslator = new Dictionary<Type, Aras.Server.Core.Configurator.DataType>
{
{ typeof(string), Aras.Server.Core.Configurator.DataType.String },
{ typeof(int), Aras.Server.Core.Configurator.DataType.Int },
{ typeof(DateTime), Aras.Server.Core.Configurator.DataType.DateTime }
};

/// <summary>
/// Initializes a new instance of the ExpressionToShortStringNotationConverter class with the defined variables container
/// </summary>
/// <param name="variablesContainer">Represents a list of “Aras.Server.Core.Configurator.Variable” objects, which will be used to get human-readable identifiers of Variables and/or Named-constants</param>
public CustomExpressionToStringNotationConverter(IEnumerable<Aras.Server.Core.Configurator.Variable> variablesContainer)
{
if (variablesContainer == null)
{
throw new ArgumentNullException(“variablesContainer”);
}

this._variablesContainer = variablesContainer;
}

/// <summary>
/// Converts an incoming “Aras.Server.Core.Configurator.ExpressionBase” Effectivity Expression object representation to a string notation and returns it.
/// </summary>
/// <param name="expression">An Effectivity Expression object representation</param>
/// <returns>A effectivity string notation value</returns>
public string ConvertExpressionToStringNotation(Aras.Server.Core.Configurator.ExpressionBase expression)
{
if (expression == null)
{
throw new ArgumentNullException(“expression”);
}

return TranslateToStringNotationImplementation(expression, null);
}

private string TranslateToStringNotationImplementation(Aras.Server.Core.Configurator.ExpressionBase expression, Aras.Server.Core.Configurator.ExpressionBase parentExpression)
{
if (expression.ExpressionType == Aras.Server.Core.Configurator.ExpressionType.Term)
{
Aras.Server.Core.Configurator.ExpressionTerm expressionTerm = expression as Aras.Server.Core.Configurator.ExpressionTerm;

string variableId = expressionTerm.OperandLeft.GetId();
Aras.Server.Core.Configurator.Variable variable = _variablesContainer.FirstOrDefault(var => var.Id == variableId);
string variableName = variable?.Name ?? variableId;

string assignedVariableValue;
Aras.Server.Core.Configurator.ITermOperand expressionTermOperandRight = expressionTerm.OperandRight;
if (expressionTermOperandRight.OperandType == Aras.Server.Core.Configurator.TermOperandType.NamedConstant)
{
string namedConstantId = expressionTermOperandRight.GetId();
assignedVariableValue = variable?.Enum?.FindNamedConstantById(namedConstantId)?.Name ?? namedConstantId;
}
else if (expressionTermOperandRight.OperandType == Aras.Server.Core.Configurator.TermOperandType.Constant)
{
object constantValue = expressionTermOperandRight.GetValue();
Type constantType = constantValue.GetType();

Aras.Server.Core.Configurator.DataType constantDataType;
if (!typeTranslator.TryGetValue(constantType, out constantDataType))
{
throw new NotSupportedException(FormattableString.Invariant($"'{constantType.FullName}' constant data type is not supported as the right operand type for string notation conversion”));
}
//'null’ as the first parameter of the ‘ToLocalString()’ method call indicates the incoming DateTime object shouldn’t be adjusted to any time zone, because it’s assumed ‘datetime’ always is returned in neutral format with UTC time zone
assignedVariableValue = constantDataType != Aras.Server.Core.Configurator.DataType.DateTime ? Convert.ToString(constantValue, CultureInfo.InvariantCulture) : Aras.I18NUtils.DateTimeConverter.ToLocalString(null, (DateTime)constantValue);
}
else
{
throw new NotSupportedException(FormattableString.Invariant($"{expressionTerm.OperandRight.OperandType} is not supported as the right operand type for string notation conversion”));
}

return string.Format(CultureInfo.InvariantCulture, "{0} {1} {2}", AddSquareBracketsIfNeed(variableName), GetAlgebraicTermOperatorNotation(expressionTerm.Operator), AddSquareBracketsIfNeed(assignedVariableValue));
}

string expressionStringNotation = string.Empty;
string expressionOperatorStrNotation = GetAlgebraicNotationByOperator(expression.ExpressionType);
//we assume at-least-one, at-most-one, exactly-one expressions consists of terms(<EQ><variable id="" /><named-constant id="" /></EQ>) only here
if (expression.ExpressionType == Aras.Server.Core.Configurator.ExpressionType.AtLeastOne ||
expression.ExpressionType == Aras.Server.Core.Configurator.ExpressionType.AtMostOne ||
expression.ExpressionType == Aras.Server.Core.Configurator.ExpressionType.ExactlyOne)
{
IEnumerable<string> innerTermsStringNotation = expression.InnerExpressions.Select(expr => TranslateToStringNotationImplementation(expr, expression));
return string.Format(CultureInfo.InvariantCulture, expressionOperatorStrNotation, string.Join(" | ", innerTermsStringNotation));
}

foreach (Aras.Server.Core.Configurator.ExpressionBase innerExpression in expression.InnerExpressions)
{
string innerExpressionStrNotation = TranslateToStringNotationImplementation(innerExpression, expression);
if (!string.IsNullOrEmpty(innerExpressionStrNotation))
{
if (expression.ExpressionType == Aras.Server.Core.Configurator.ExpressionType.NOT)
{
expressionStringNotation += string.Format(CultureInfo.InvariantCulture, expressionOperatorStrNotation, innerExpressionStrNotation);
}
else if (expression.ExpressionType == Aras.Server.Core.Configurator.ExpressionType.IMPLICATION)
{
string[] ifThen = expressionOperatorStrNotation.Split(' ');
bool isStringNotationEmpty = string.IsNullOrEmpty(expressionStringNotation);

// implicationTemplate describes “IF {expr}" or “THEN {expr}". In case “IF”, whitespace after "{expr}" is required -"IF {expr} "
string implicationTemplate = isStringNotationEmpty ? "{0} {1} " : "{0} {1}";
expressionStringNotation += string.Format(CultureInfo.InvariantCulture, implicationTemplate, ifThen[isStringNotationEmpty ? 0 : 1], innerExpressionStrNotation);
}
else
{
expressionStringNotation += !string.IsNullOrEmpty(expressionStringNotation) ? expressionOperatorStrNotation + innerExpressionStrNotation : innerExpressionStrNotation;
}
}
}

return parentExpression != null && parentExpression.InnerExpressions.Count > 1 &&
parentExpression.ExpressionType != Aras.Server.Core.Configurator.ExpressionType.IMPLICATION && expression.ExpressionType != Aras.Server.Core.Configurator.ExpressionType.NOT ?
"(" + expressionStringNotation + ")” : expressionStringNotation;
}

private static string AddSquareBracketsIfNeed(string name)
{
return !string.IsNullOrEmpty(name) && !System.Text.RegularExpressions.Regex.IsMatch(name, @"^[a-zA-Z0-9]+$") ?
string.Format(CultureInfo.InvariantCulture, "[{0}]”, name) : name;
}

private static string GetAlgebraicNotationByOperator(Aras.Server.Core.Configurator.ExpressionType boolOperator)
{
string notation = string.Empty;
switch (boolOperator)
{
case Aras.Server.Core.Configurator.ExpressionType.AND:
notation = " && ";
break;
case Aras.Server.Core.Configurator.ExpressionType.OR:
notation = " || ";
break;
case Aras.Server.Core.Configurator.ExpressionType.NOT:
notation = "!({0})";
break;
case Aras.Server.Core.Configurator.ExpressionType.IMPLICATION:
notation = “IF THEN";
break;
case Aras.Server.Core.Configurator.ExpressionType.AtLeastOne:
notation = “AT-LEAST-ONE({0})";
break;
case Aras.Server.Core.Configurator.ExpressionType.AtMostOne:
notation = “AT-MOST-ONE({0})";
break;
case Aras.Server.Core.Configurator.ExpressionType.ExactlyOne:
notation = “EXACTLY-ONE({0})";
break;
default:
throw new NotSupportedException(boolOperator + " type is not supported for string notation conversion”);
}

return notation;
}

private static string GetAlgebraicTermOperatorNotation(Aras.Server.Core.Configurator.TermOperator termOperator)
{
string algebraicTermOperatorNotation;
switch (termOperator)
{
case Aras.Server.Core.Configurator.TermOperator.Equal:
algebraicTermOperatorNotation = "=";
break;
case Aras.Server.Core.Configurator.TermOperator.GreaterThanOrEqual:
algebraicTermOperatorNotation = ">=";
break;
case Aras.Server.Core.Configurator.TermOperator.LessThanOrEqual:
algebraicTermOperatorNotation = "<=";
break;
default:
throw new NotSupportedException(FormattableString.Invariant($"'{termOperator}' term operator is not supported for string notation conversion”));
}

return algebraicTermOperatorNotation;
}
}

Custom Unified Effectivity String Notation

By default, a comma (,) is used to combine multiple Effectivities on a single Relationship. A customized String Notation uses the OR conjunction word instead of the comma.

Table 4: A customized String Notation example

Effective ItemSingleEffectivity String NotationsUnifiedEffectivity String Notation
DefaultCustomized
EngineFactory = Detroit(Factory = Detroit),(Factory = Munich)(Factory = Detroit)OR(Factory = Munich)
Factory = Munich

JoinExpressionStringNotation Method Definition

To achieve this customization goal, change the definition of the JoinExpressionStringNotation internal method of the effs_getExpressionStringNotation Method as follows:

internal virtual string JoinExpressionStringNotation(IEnumerable<string> expressionStringNotations)
{
if (!string.IsNullOrEmpty(expressionStringNotations.ElementAtOrDefault(1)))
{
expressionStringNotations = expressionStringNotations.Select(
expressionStringNotation => "(" + expressionStringNotation + ")”);
}
return string.Join(" OR ", expressionStringNotations);
}