use of org.eclipse.titan.designer.AST.TTCN3.values.Values in project titan.EclipsePlug-ins by eclipse.
the class BrokenPartsViaReferences method buildInvertedImportStructure.
/**
* It is build an inverted import structure and identify startmodules whose CompilationTimeStamp is null.
*
* @param allModules
* the list of modules to be check. Initially all modules.
* @param startModules
* the list of modules to be check. Initially all modules, but the function will remove those that can be skipped.
*
* @return invertedImports contains the next:<br>
* - key: a module.<br>
* - values: in these modules the key module is used, so all values imported this module, it is an inverted "imported" connection.<br>
* If module A import B, C, D and module B import D, E then the next structure will be built:<br>
* A = [],<br>
* B = [A],<br>
* C = [A],<br>
* D = [A, B],<br>
* E = [B]<br>
*/
protected Map<Module, List<Module>> buildInvertedImportStructure(final List<Module> allModules, final List<Module> startModules) {
final Map<Module, List<Module>> invertedImports = new HashMap<Module, List<Module>>();
for (Module actualModule : allModules) {
// It covers the case when a module is a top-level module, so it has't got any import.
if (!invertedImports.containsKey(actualModule)) {
invertedImports.put(actualModule, new ArrayList<Module>());
}
for (Module actualImportedModule : actualModule.getImportedModules()) {
if (invertedImports.containsKey(actualImportedModule)) {
final List<Module> dependentModules = invertedImports.get(actualImportedModule);
if (!dependentModules.contains(actualModule)) {
dependentModules.add(actualModule);
}
} else {
final List<Module> temp = new ArrayList<Module>();
temp.add(actualModule);
invertedImports.put(actualImportedModule, temp);
}
}
}
return invertedImports;
}
use of org.eclipse.titan.designer.AST.TTCN3.values.Values in project titan.EclipsePlug-ins by eclipse.
the class ErroneousAttributes method buildErroneousDescriptorTree.
private ErroneousDescriptor buildErroneousDescriptorTree(final CompilationTimeStamp timestamp, final List<FieldErr_Type> fldArray, final int level) {
final ErroneousDescriptor erroneousDescr = new ErroneousDescriptor();
Qualifier omitBeforeQualifier = null;
Qualifier omitAfterQualifier = null;
final Map<Integer, List<FieldErr_Type>> embeddedFieldArrayMap = new HashMap<Integer, List<FieldErr_Type>>();
for (FieldErr_Type actualFieldErr : fldArray) {
if (actualFieldErr.subrefsArray.size() <= level) {
ErrorReporter.INTERNAL_ERROR();
return erroneousDescr;
}
final int fieldIndex = actualFieldErr.subrefsArray.get(level);
final IType fieldType = actualFieldErr.typeArray.get(level);
if (omitBeforeQualifier != null && erroneousDescr.omitBefore != -1 && erroneousDescr.omitBefore > fieldIndex) {
final String message = MessageFormat.format("Field `{0}'' cannot be referenced because all fields before field `{1}'' have been omitted", actualFieldErr.qualifier.getDisplayName(), omitBeforeQualifier.getDisplayName());
actualFieldErr.qualifier.getLocation().reportSemanticError(message);
continue;
}
if (omitAfterQualifier != null && erroneousDescr.omitAfter != -1 && erroneousDescr.omitAfter < fieldIndex) {
final String message = MessageFormat.format("Field `{0}'' cannot be referenced because all fields after field `{1}'' have been omitted", actualFieldErr.qualifier.getDisplayName(), omitAfterQualifier.getDisplayName());
actualFieldErr.qualifier.getLocation().reportSemanticError(message);
continue;
}
final Indicator_Type actIndicator = actualFieldErr.errAttrSpec.getIndicator();
final boolean isOmit = actualFieldErr.errAttrSpec.isOmit();
if (actualFieldErr.subrefsArray.size() == level + 1) {
// erroneous value
if (actualFieldErr.typeArray.size() != level + 1) {
ErrorReporter.INTERNAL_ERROR();
return erroneousDescr;
}
if (fieldType.getTypetype() == Type_type.TYPE_ASN1_SET && isOmit && actIndicator != Indicator_Type.Value_Indicator) {
final String message = MessageFormat.format("Cannot omit all fields {0} `{1}'' which is a field of an ASN.1 SET type. " + "The order of fields in ASN.1 SET types changes depending on tagging (see X.690 9.3). " + "Fields can be omitted individually, independently of the field order which depends on tagging", actIndicator.getDisplayName(), actualFieldErr.qualifier.getDisplayName());
actualFieldErr.qualifier.getLocation().reportSemanticError(message);
continue;
}
switch(fieldType.getTypetypeTtcn3()) {
case TYPE_TTCN3_CHOICE:
if (actIndicator != Indicator_Type.Value_Indicator) {
final String message = MessageFormat.format("Indicator `{0}'' cannot be used with reference `{1}'' which points to a field of a union type", actIndicator.getDisplayName(), actualFieldErr.qualifier.getDisplayName());
actualFieldErr.qualifier.getLocation().reportSemanticError(message);
continue;
}
break;
case TYPE_TTCN3_SEQUENCE:
case TYPE_TTCN3_SET:
if (isOmit && actIndicator == Indicator_Type.After_Indicator) {
int lastFieldIndex;
switch(fieldType.getTypetype()) {
case TYPE_ASN1_SEQUENCE:
lastFieldIndex = ((ASN1_Sequence_Type) fieldType).getNofComponents(timestamp) - 1;
break;
case TYPE_ASN1_SET:
lastFieldIndex = ((ASN1_Set_Type) fieldType).getNofComponents(timestamp) - 1;
break;
default:
lastFieldIndex = ((TTCN3_Set_Seq_Choice_BaseType) fieldType).getNofComponents() - 1;
}
if (fieldIndex == lastFieldIndex) {
final String message = MessageFormat.format("There is nothing to omit after the last field ({0}) of a record/set type", actualFieldErr.qualifier.getDisplayName());
actualFieldErr.qualifier.getLocation().reportSemanticError(message);
continue;
}
}
// $FALL-THROUGH$
case TYPE_SEQUENCE_OF:
case TYPE_SET_OF:
if (isOmit && actIndicator == Indicator_Type.Before_Indicator && fieldIndex == 0) {
actualFieldErr.qualifier.getLocation().reportSemanticError(MessageFormat.format("There is nothing to omit before the first field ({0})", actualFieldErr.qualifier.getDisplayName()));
continue;
}
break;
default:
break;
}
// check for duplicate value+indicator
if (erroneousDescr.valuesMap.containsKey(fieldIndex)) {
final ErroneousValues evs = erroneousDescr.valuesMap.get(fieldIndex);
if ((evs.before != null && actIndicator == Indicator_Type.Before_Indicator) || (evs.value != null && actIndicator == Indicator_Type.Value_Indicator) || (evs.after != null && actIndicator == Indicator_Type.After_Indicator)) {
actualFieldErr.qualifier.getLocation().reportSemanticError(MessageFormat.format("Duplicate reference to field `{0}'' with indicator `{1}''", actualFieldErr.qualifier.getDisplayName(), actIndicator.getDisplayName()));
continue;
}
}
// values were used
if (actIndicator == Indicator_Type.Value_Indicator && embeddedFieldArrayMap.containsKey(fieldIndex)) {
final String message = MessageFormat.format("Reference to field `{0}'' with indicator `value'' would invalidate previously specified erroneous data", actualFieldErr.qualifier.getDisplayName());
actualFieldErr.qualifier.getLocation().reportSemanticError(message);
continue;
}
// duplication of omit before/after rule
if (actIndicator == Indicator_Type.Before_Indicator && isOmit) {
if (omitBeforeQualifier != null && erroneousDescr.omitBefore != -1) {
final String message = MessageFormat.format("Duplicate rule for omitting all fields before the specified field. " + "Used on field `{0}'' but previously already used on field `{1}''", actualFieldErr.qualifier.getDisplayName(), omitBeforeQualifier.getDisplayName());
actualFieldErr.qualifier.getLocation().reportSemanticError(message);
continue;
}
boolean isInvalid = false;
for (Integer idx : erroneousDescr.valuesMap.keySet()) {
if (idx < fieldIndex) {
isInvalid = true;
break;
}
}
if (!isInvalid) {
for (Integer idx : embeddedFieldArrayMap.keySet()) {
if (idx < fieldIndex) {
isInvalid = true;
break;
}
}
}
if (isInvalid) {
final String message = MessageFormat.format("Omitting fields before field `{0}'' would invalidate previously specified erroneous data", actualFieldErr.qualifier.getDisplayName());
actualFieldErr.qualifier.getLocation().reportSemanticError(message);
continue;
}
// save valid omit before data
omitBeforeQualifier = actualFieldErr.qualifier;
erroneousDescr.omitBefore = fieldIndex;
erroneousDescr.omitBeforeName = omitBeforeQualifier.getDisplayName();
continue;
}
if (actIndicator == Indicator_Type.After_Indicator && isOmit) {
if (omitAfterQualifier != null && erroneousDescr.omitAfter != -1) {
final String message = MessageFormat.format("Duplicate rule for omitting all fields after the specified field. " + "Used on field `{0}'' but previously already used on field `{1}''", actualFieldErr.qualifier.getDisplayName(), omitAfterQualifier.getDisplayName());
actualFieldErr.qualifier.getLocation().reportSemanticError(message);
continue;
}
boolean isInvalid = false;
for (Integer idx : erroneousDescr.valuesMap.keySet()) {
if (idx > fieldIndex) {
isInvalid = true;
break;
}
}
if (!isInvalid) {
for (Integer idx : embeddedFieldArrayMap.keySet()) {
if (idx > fieldIndex) {
isInvalid = true;
break;
}
}
}
if (isInvalid) {
final String message = MessageFormat.format("Omitting fields after field `{0}'' would invalidate previously specified erroneous data", actualFieldErr.qualifier.getDisplayName());
actualFieldErr.qualifier.getLocation().reportSemanticError(message);
continue;
}
// save valid omit after data
omitAfterQualifier = actualFieldErr.qualifier;
erroneousDescr.omitAfter = fieldIndex;
erroneousDescr.omitAfterName = omitAfterQualifier.getDisplayName();
continue;
}
// if not before/after omit then save this into
// values_m
final boolean hasKey = erroneousDescr.valuesMap.containsKey(fieldIndex);
final ErroneousValues evs = hasKey ? erroneousDescr.valuesMap.get(fieldIndex) : new ErroneousValues(actualFieldErr.qualifier.getDisplayName());
switch(actIndicator) {
case Before_Indicator:
evs.before = actualFieldErr.errAttrSpec;
break;
case Value_Indicator:
evs.value = actualFieldErr.errAttrSpec;
break;
case After_Indicator:
evs.after = actualFieldErr.errAttrSpec;
break;
default:
ErrorReporter.INTERNAL_ERROR();
}
if (!hasKey) {
erroneousDescr.valuesMap.put(fieldIndex, evs);
}
} else {
// embedded err.value
if (erroneousDescr.valuesMap.containsKey(fieldIndex) && erroneousDescr.valuesMap.get(fieldIndex).value != null) {
final String message = MessageFormat.format("Field `{0}'' is embedded into a field which was previously overwritten or omitted", actualFieldErr.qualifier.getDisplayName());
actualFieldErr.qualifier.getLocation().reportSemanticError(message);
continue;
}
// add the embedded field to the map
final boolean hasIndex = embeddedFieldArrayMap.containsKey(fieldIndex);
final List<FieldErr_Type> embeddedFieldArray = hasIndex ? embeddedFieldArrayMap.get(fieldIndex) : new ArrayList<FieldErr_Type>(1);
embeddedFieldArray.add(actualFieldErr);
if (!hasIndex) {
embeddedFieldArrayMap.put(fieldIndex, embeddedFieldArray);
}
}
}
// recursive calls to create embedded descriptors
for (Integer idx : embeddedFieldArrayMap.keySet()) {
erroneousDescr.descriptorMap.put(idx, buildErroneousDescriptorTree(timestamp, embeddedFieldArrayMap.get(idx), level + 1));
}
return erroneousDescr;
}
use of org.eclipse.titan.designer.AST.TTCN3.values.Values in project titan.EclipsePlug-ins by eclipse.
the class TTCN3_Enumerated_Type method check.
/**
* Does the semantic checking of the enumerations.
*
* @param timestamp the timestamp of the actual semantic check cycle.
*/
@Override
public /**
* {@inheritDoc}
*/
void check(final CompilationTimeStamp timestamp) {
if (lastTimeChecked != null && !lastTimeChecked.isLess(timestamp)) {
return;
}
parseAttributes(timestamp);
nameMap = new HashMap<String, EnumItem>(items.getItems().size());
final Map<Long, EnumItem> valueMap = new HashMap<Long, EnumItem>(items.getItems().size());
final List<EnumItem> enumItems = items.getItems();
// check duplicated names and values
for (int i = 0, size = enumItems.size(); i < size; i++) {
final EnumItem item = enumItems.get(i);
final Identifier id = item.getId();
final String fieldName = id.getName();
if (nameMap.containsKey(fieldName)) {
nameMap.get(fieldName).getId().getLocation().reportSingularSemanticError(MessageFormat.format(DUPLICATEENUMERATIONIDENTIFIERFIRST, id.getDisplayName()));
id.getLocation().reportSemanticError(MessageFormat.format(DUPLICATEENUMERATIONIDENTIFIERREPEATED, id.getDisplayName()));
} else {
nameMap.put(fieldName, item);
}
final Value value = item.getValue();
if (value != null && item.isOriginal()) {
if (value.getIsErroneous(timestamp) || !Value_type.INTEGER_VALUE.equals(value.getValuetype())) {
value.getLocation().reportSemanticError(MessageFormat.format("INTEGER value was expected for enumeration `{0}''", id.getDisplayName()));
setIsErroneous(true);
} else {
final Integer_Value enumValue = (Integer_Value) value;
if (!enumValue.isNative()) {
enumValue.getLocation().reportSemanticError(MessageFormat.format(LARGEINTEGERERROR, enumValue.getValueValue()));
setIsErroneous(true);
} else {
final Long enumLong = enumValue.getValue();
if (valueMap.containsKey(enumLong)) {
valueMap.get(enumLong).getLocation().reportSingularSemanticError(MessageFormat.format(DUPLICATEDENUMERATIONVALUEFIRST, enumLong, valueMap.get(enumLong).getId().getDisplayName()));
value.getLocation().reportSemanticError(MessageFormat.format(DUPLICATEDENUMERATIONVALUEREPEATED, enumLong, id.getDisplayName()));
setIsErroneous(true);
} else {
valueMap.put(enumLong, item);
}
}
}
}
}
// Assign default values
if (!getIsErroneous(timestamp) && lastTimeChecked == null) {
Long firstUnused = Long.valueOf(0);
while (valueMap.containsKey(firstUnused)) {
firstUnused++;
}
for (int i = 0, size = enumItems.size(); i < size; i++) {
final EnumItem item = enumItems.get(i);
if (!item.isOriginal()) {
// optimization: if the same value was already assigned, there is no need to create it again.
final IValue value = item.getValue();
if (value == null || ((Integer_Value) value).getValue() != firstUnused) {
final Integer_Value tempValue = new Integer_Value(firstUnused.longValue());
tempValue.setLocation(item.getLocation());
item.setValue(tempValue);
}
valueMap.put(firstUnused, item);
firstUnused = Long.valueOf(firstUnused.longValue() + 1);
while (valueMap.containsKey(firstUnused)) {
firstUnused++;
}
}
}
}
valueMap.clear();
lastTimeChecked = timestamp;
checkSubtypeRestrictions(timestamp);
if (myScope != null) {
checkEncode(timestamp);
checkVariants(timestamp);
}
}
use of org.eclipse.titan.designer.AST.TTCN3.values.Values in project titan.EclipsePlug-ins by eclipse.
the class SequenceOf_Type method checkThisValueSequenceOf.
/**
* Checks the SequenceOf_value kind value against this type.
* <p>
* Please note, that this function can only be called once we know for sure
* that the value is of set-of type.
*
* @param timestamp the timestamp of the actual semantic check cycle.
* @param value the value to be checked
* @param expectedValue the kind of value expected here.
* @param incompleteAllowed wheather incomplete value is allowed or not.
* @param implicitOmit true if the implicit omit optional attribute was set
* for the value, false otherwise
*/
public boolean checkThisValueSequenceOf(final CompilationTimeStamp timestamp, final SequenceOf_Value value, final Assignment lhs, final Expected_Value_type expectedValue, final boolean incompleteAllowed, final boolean implicitOmit, final boolean strElem) {
boolean selfReference = false;
if (value.isIndexed()) {
boolean checkHoles = Expected_Value_type.EXPECTED_CONSTANT.equals(expectedValue);
BigInteger maxIndex = BigInteger.valueOf(-1);
final Map<BigInteger, Integer> indexMap = new HashMap<BigInteger, Integer>(value.getNofComponents());
for (int i = 0, size = value.getNofComponents(); i < size; i++) {
final IValue component = value.getValueByIndex(i);
final IValue index = value.getIndexByIndex(i);
final IReferenceChain referenceChain = ReferenceChain.getInstance(IReferenceChain.CIRCULARREFERENCE, true);
final IValue indexLast = index.getValueRefdLast(timestamp, referenceChain);
referenceChain.release();
final IType tempType = TypeFactory.createType(Type_type.TYPE_INTEGER);
tempType.check(timestamp);
indexLast.setMyGovernor(tempType);
final IValue temporalValue = tempType.checkThisValueRef(timestamp, indexLast);
tempType.checkThisValue(timestamp, temporalValue, lhs, new ValueCheckingOptions(Expected_Value_type.EXPECTED_DYNAMIC_VALUE, true, false, true, false, false));
if (indexLast.getIsErroneous(timestamp) || !Value_type.INTEGER_VALUE.equals(temporalValue.getValuetype())) {
checkHoles = false;
} else {
final BigInteger tempIndex = ((Integer_Value) temporalValue).getValueValue();
if (tempIndex.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) == 1) {
index.getLocation().reportSemanticError(MessageFormat.format("A integer value less than `{0}'' was expected for indexing type `{1}'' instead of `{2}''", Integer.MAX_VALUE, getTypename(), tempIndex));
checkHoles = false;
} else if (tempIndex.compareTo(BigInteger.ZERO) == -1) {
index.getLocation().reportSemanticError(MessageFormat.format("A non-negative integer value was expected for indexing type `{0}'' instead of `{1}''", getTypename(), tempIndex));
checkHoles = false;
} else if (indexMap.containsKey(tempIndex)) {
index.getLocation().reportSemanticError(MessageFormat.format("Duplicate index value `{0}'' for components {1} and {2}", tempIndex, indexMap.get(tempIndex), i + 1));
checkHoles = false;
} else {
indexMap.put(tempIndex, Integer.valueOf(i + 1));
if (maxIndex.compareTo(tempIndex) == -1) {
maxIndex = tempIndex;
}
}
}
component.setMyGovernor(getOfType());
final IValue tempValue2 = getOfType().checkThisValueRef(timestamp, component);
selfReference |= getOfType().checkThisValue(timestamp, tempValue2, lhs, new ValueCheckingOptions(expectedValue, incompleteAllowed, false, true, implicitOmit, strElem));
}
if (checkHoles) {
if (maxIndex.compareTo(BigInteger.valueOf(indexMap.size() - 1)) != 0) {
value.getLocation().reportSemanticError("It's not allowed to create hole(s) in constant values");
}
}
} else {
for (int i = 0, size = value.getNofComponents(); i < size; i++) {
final IValue component = value.getValueByIndex(i);
component.setMyGovernor(getOfType());
if (Value_type.NOTUSED_VALUE.equals(component.getValuetype())) {
if (!incompleteAllowed) {
component.getLocation().reportSemanticError(INCOMPLETEPRESENTERROR);
}
} else {
final IValue tempValue2 = getOfType().checkThisValueRef(timestamp, component);
selfReference |= getOfType().checkThisValue(timestamp, tempValue2, lhs, new ValueCheckingOptions(expectedValue, incompleteAllowed, false, true, implicitOmit, strElem));
}
}
}
value.setLastTimeChecked(timestamp);
return selfReference;
}
use of org.eclipse.titan.designer.AST.TTCN3.values.Values in project titan.EclipsePlug-ins by eclipse.
the class Array_Type method checkThisValueArray.
private boolean checkThisValueArray(final CompilationTimeStamp timestamp, final IValue originalValue, final Assignment lhs, final Array_Value lastValue, final Expected_Value_type expectedValue, final boolean incompleteAllowed, final boolean implicitOmit, final boolean strElem) {
if (dimension == null) {
return false;
}
boolean selfReference = false;
final int nofValues = lastValue.getNofComponents();
if (!dimension.getIsErroneous(timestamp) && dimension.getSize() < nofValues) {
originalValue.getLocation().reportSemanticError(MessageFormat.format(TOOMANYEXPECTED, dimension.getSize(), nofValues));
originalValue.setIsErroneous(true);
}
if (lastValue.isIndexed()) {
boolean checkHoles = !dimension.getIsErroneous(timestamp) && Expected_Value_type.EXPECTED_CONSTANT.equals(expectedValue);
final long arraySize = dimension.getSize();
BigInteger maxIndex = BigInteger.valueOf(-1);
final Map<BigInteger, Integer> indexMap = new HashMap<BigInteger, Integer>(lastValue.getNofComponents());
for (int i = 0, size = lastValue.getNofComponents(); i < size; i++) {
final IValue component = lastValue.getValueByIndex(i);
final Value index = lastValue.getIndexByIndex(i);
dimension.checkIndex(timestamp, index, expectedValue);
final IReferenceChain referenceChain = ReferenceChain.getInstance(IReferenceChain.CIRCULARREFERENCE, true);
final IValue indexLast = index.getValueRefdLast(timestamp, referenceChain);
referenceChain.release();
if (indexLast.getIsErroneous(timestamp) || !Value_type.INTEGER_VALUE.equals(indexLast.getValuetype())) {
checkHoles = false;
} else {
final BigInteger tempIndex = ((Integer_Value) indexLast).getValueValue();
if (tempIndex.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) == 1) {
index.getLocation().reportSemanticError(MessageFormat.format("A integer value less than `{0}'' was expected for indexing type `{1}'' instead of `{2}''", Integer.MAX_VALUE, getTypename(), tempIndex));
checkHoles = false;
} else if (tempIndex.compareTo(BigInteger.ZERO) == -1) {
index.getLocation().reportSemanticError(MessageFormat.format("A non-negative integer value was expected for indexing type `{0}'' instead of `{1}''", getTypename(), tempIndex));
checkHoles = false;
} else if (indexMap.containsKey(tempIndex)) {
index.getLocation().reportSemanticError(MessageFormat.format("Duplicate index value `{0}'' for components {1} and {2}", tempIndex, indexMap.get(tempIndex), i + 1));
checkHoles = false;
} else {
indexMap.put(tempIndex, Integer.valueOf(i + 1));
if (maxIndex.compareTo(tempIndex) == -1) {
maxIndex = tempIndex;
}
}
}
component.setMyGovernor(elementType);
final IValue tempValue2 = elementType.checkThisValueRef(timestamp, component);
selfReference |= elementType.checkThisValue(timestamp, tempValue2, lhs, new ValueCheckingOptions(expectedValue, incompleteAllowed, false, true, implicitOmit, strElem));
}
if (checkHoles) {
if (indexMap.size() < arraySize) {
lastValue.getLocation().reportSemanticError("It's not allowed to create hole(s) in constant values");
originalValue.setIsErroneous(true);
}
}
} else {
if (!dimension.getIsErroneous(timestamp)) {
final long arraySize = dimension.getSize();
if (arraySize > nofValues) {
originalValue.getLocation().reportSemanticError(MessageFormat.format("Too few elements in the array value: {0} was expected instead of {1}", arraySize, nofValues));
originalValue.setIsErroneous(true);
} else if (arraySize < nofValues) {
originalValue.getLocation().reportSemanticError(MessageFormat.format("Too many elements in the array value: {0} was expected instead of {1}", arraySize, nofValues));
originalValue.setIsErroneous(true);
}
}
for (int i = 0, size = lastValue.getNofComponents(); i < size; i++) {
final IValue component = lastValue.getValueByIndex(i);
component.setMyGovernor(elementType);
if (Value_type.NOTUSED_VALUE.equals(component.getValuetype())) {
if (!incompleteAllowed) {
component.getLocation().reportSemanticError(AbstractOfType.INCOMPLETEPRESENTERROR);
}
} else {
final IValue tempValue2 = elementType.checkThisValueRef(timestamp, component);
selfReference |= elementType.checkThisValue(timestamp, tempValue2, lhs, new ValueCheckingOptions(expectedValue, incompleteAllowed, false, true, implicitOmit, strElem));
}
}
}
return selfReference;
}
Aggregations