PropertiesValidator.java

/**
 * Copyright (c) 2004-2025 Carnegie Mellon University and others. (see Contributors file).
 * All Rights Reserved.
 *
 * NO WARRANTY. ALL MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
 * KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE
 * OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT
 * MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 *
 * This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 * SPDX-License-Identifier: EPL-2.0
 *
 * Created, in part, with funding and support from the United States Government. (see Acknowledgments file).
 *
 * This program includes and/or can make use of certain third party source code, object code, documentation and other
 * files ("Third Party Software"). The Third Party Software that is used by this program is dependent upon your system
 * configuration. By using this program, You agree to comply with any and all relevant Third Party Software terms and
 * conditions contained in any such Third Party Software or separate license file distributed with such Third Party
 * Software. The parties who own the Third Party Software ("Third Party Licensors") are intended third party benefici-
 * aries to this license with respect to the terms applicable to their Third Party Software. Third Party Software li-
 * censes only apply to the Third Party Software and not any other portion of this program or this program as a whole.
 */
package org.osate.xtext.aadl2.properties.validation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.OptionalLong;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.diagnostics.Diagnostic;
import org.eclipse.xtext.validation.Check;
import org.eclipse.xtext.validation.CheckType;
import org.eclipse.xtext.validation.ValidationMessageAcceptor;
import org.eclipse.xtext.xbase.lib.StringExtensions;
import org.osate.aadl2.Aadl2Package;
import org.osate.aadl2.AadlBoolean;
import org.osate.aadl2.AadlInteger;
import org.osate.aadl2.AadlPackage;
import org.osate.aadl2.AadlReal;
import org.osate.aadl2.AadlString;
import org.osate.aadl2.AbstractNamedValue;
import org.osate.aadl2.ArrayDimension;
import org.osate.aadl2.ArrayRange;
import org.osate.aadl2.ArraySizeProperty;
import org.osate.aadl2.ArrayableElement;
import org.osate.aadl2.BasicPropertyAssociation;
import org.osate.aadl2.BooleanLiteral;
import org.osate.aadl2.Classifier;
import org.osate.aadl2.ClassifierType;
import org.osate.aadl2.ClassifierValue;
import org.osate.aadl2.ComponentClassifier;
import org.osate.aadl2.ComponentImplementation;
import org.osate.aadl2.ComponentType;
import org.osate.aadl2.ContainedNamedElement;
import org.osate.aadl2.ContainmentPathElement;
import org.osate.aadl2.Element;
import org.osate.aadl2.EnumerationLiteral;
import org.osate.aadl2.EnumerationType;
import org.osate.aadl2.IntegerLiteral;
import org.osate.aadl2.InternalFeature;
import org.osate.aadl2.ListType;
import org.osate.aadl2.ListValue;
import org.osate.aadl2.MetaclassReference;
import org.osate.aadl2.ModalPropertyValue;
import org.osate.aadl2.Mode;
import org.osate.aadl2.ModelUnit;
import org.osate.aadl2.NamedElement;
import org.osate.aadl2.NamedValue;
import org.osate.aadl2.Namespace;
import org.osate.aadl2.NumberType;
import org.osate.aadl2.NumberValue;
import org.osate.aadl2.NumericRange;
import org.osate.aadl2.Operation;
import org.osate.aadl2.PackageSection;
import org.osate.aadl2.ProcessorFeature;
import org.osate.aadl2.Property;
import org.osate.aadl2.PropertyAssociation;
import org.osate.aadl2.PropertyConstant;
import org.osate.aadl2.PropertyExpression;
import org.osate.aadl2.PropertySet;
import org.osate.aadl2.PropertyType;
import org.osate.aadl2.RangeType;
import org.osate.aadl2.RangeValue;
import org.osate.aadl2.RealLiteral;
import org.osate.aadl2.RecordType;
import org.osate.aadl2.RecordValue;
import org.osate.aadl2.ReferenceType;
import org.osate.aadl2.ReferenceValue;
import org.osate.aadl2.StringLiteral;
import org.osate.aadl2.Subcomponent;
import org.osate.aadl2.UnitLiteral;
import org.osate.aadl2.UnitsType;
import org.osate.aadl2.modelsupport.util.AadlUtil;
import org.osate.aadl2.properties.EvaluatedProperty.MpvProxy;
import org.osate.aadl2.properties.InvalidModelException;
import org.osate.aadl2.properties.PropertyAcc;
import org.osate.aadl2.util.Aadl2Util;

/**
 * @since 3.0
 */
public class PropertiesValidator extends AbstractPropertiesValidator {

	public static final String INVALID_ASSIGNMENT = "edu.cmu.sei.invalid.assignment";
	public static final String MISSING_WITH = "org.osate.xtext.aadl2.properties.missing_with";
	public static final String UPPER_LESS_THAN_LOWER = "org.osate.xtext.aadl2.properties.upper_less_than_lower";
	public static final String DELTA_NEGATIVE = "org.osate.xtext.aadl2.properties.delta_negative";
	public static final String MISSING_NUMBERVALUE_UNITS = "org.osate.xtext.aadl2.properties.missing_numbervalue_units";
	public static final String SEI_DATA_RATE_DEPRECATED = "org.osate.xtext.aadl2.properties.sei_data_rate_deprecated";
	public static final String BYTE_COUNT_DEPRECATED = "org.osate.xtext.aadl2.properties.byte_count_deprecated";
	public static final String SOURCE_DATA_SIZE_DEPRECATED = "org.osate.xtext.aadl2.properties.source_data_size_deprecated";
	public static final String SOURCE_CODE_SIZE_DEPRECATED = "org.osate.xtext.aadl2.properties.source_code_size_deprecated";
	public static final String SOURCE_HEAP_SIZE_DEPRECATED = "org.osate.xtext.aadl2.properties.source_heap_size_deprecated";
	public static final String SOURCE_STACK_SIZE_DEPRECATED = "org.osate.xtext.aadl2.properties.source_stack_size_deprecated";
	public static final String DATA_VOLUME_DEPRECATED = "org.osate.xtext.aadl2.properties.data_volume_deprecated";

	public static final String ARRAY_RANGE_UPPER_LESS_THAN_LOWER = "org.osate.xtext.aadl2.properties.array_range_upper_less_than_lower";
	public static final String ARRAY_RANGE_UPPER_GREATER_THAN_MAXIMUM = "org.osate.xtext.aadl2.properties.array_range_upper_greater_than_maximum";
	public static final String ARRAY_INDEX_GREATER_THAN_MAXIMUM = "org.osate.xtext.aadl2.properties.array_rindex_greater_than_maximum";
	public static final String ARRAY_LOWER_BOUND_IS_ZERO = "org.osate.xtext.aadl2.properties.array_lower_bound_is_zero";

	@Check(CheckType.FAST)
	public void caseRangeValue(final RangeValue rv) {
		final NumberValue deltaNV = rv.getDeltaValue();
		if (deltaNV != null) {
			final double delta = deltaNV.getScaledValue();
			if (delta < 0) {
				error("Range value has a negative delta component", rv.getDelta(), null, DELTA_NEGATIVE);
			}
		}
		final NumberValue lower = rv.getMinimumValue();
		final NumberValue upper = rv.getMaximumValue();
		if (lower != null && upper != null) {
			final double lowerScaled = lower.getScaledValue();
			final double upperScaled = upper.getScaledValue();
			if (upperScaled < lowerScaled) {
				error("Upper bound of range is less than the lower bound.", rv, null, UPPER_LESS_THAN_LOWER);
			}
		}
	}

	@Check(CheckType.FAST)
	public void casePropertyAssociation(PropertyAssociation pa) {
		checkPropertyAssociation(pa);
		checkPropertyAssociationModalBindings(pa);
		checkPropertyMissingModes(pa);
		checkModalAppliesTo(pa);
		checkContainedProperties(pa);
		checkForAppendsInContainedPropertyAssociation(pa);
	}

	public void checkForAppendsInContainedPropertyAssociation(PropertyAssociation propertyAssoc) {
		List<ContainedNamedElement> appliesTos = propertyAssoc.getAppliesTos();
		if (null != appliesTos && !appliesTos.isEmpty()) {
			if (propertyAssoc.isAppend()) {
				error("Append operator '+=>' cannot be used in contained property associations", propertyAssoc,
						Aadl2Package.eINSTANCE.getPropertyAssociation_Append());
			}
		}
	}

	/**
	 * Check Classifier reference for right type.
	 */

	@Check(CheckType.FAST)
	public void caseClassifierValue(ClassifierValue nt) {
		checkClassifierReferenceInWith(nt.getClassifier(), nt);
	}

	@Check(CheckType.FAST)
	public void caseContainmentPathElement(ContainmentPathElement pathElement) {
		typeCheckContainmentPathElement(pathElement);
	}

	@Check(CheckType.FAST)
	public void caseRecordValue(RecordValue recordValue) {
		checkDuplicateFieldAssignment(recordValue);
	}

	@Check(CheckType.FAST)
	public void checkArrayReference(ContainmentPathElement pathElement) {
		NamedElement element = pathElement.getNamedElement();
		List<ArrayRange> providedRanges = pathElement.getArrayRanges();
		if (element.eIsProxy() || providedRanges.isEmpty()) {
			// Only validate if the name is resolvable and there really are array indicies.
			return;
		}
		String name = element.getName();
		if (element instanceof ArrayableElement) {
			List<ArrayDimension> requiredDimensions = ((ArrayableElement) element).getArrayDimensions();
			if (requiredDimensions.isEmpty()) {
				error(providedRanges.get(0), "'" + name + "' is not an array");
			} else if (providedRanges.size() < requiredDimensions.size()) {
				error(providedRanges.get(providedRanges.size() - 1),
						"Too few array dimensions: '" + name + "' has " + requiredDimensions.size());
			} else if (providedRanges.size() > requiredDimensions.size()) {
				error(providedRanges.get(requiredDimensions.size()),
						"Too many array dimensions: '" + name + "' has " + requiredDimensions.size());
			} else {
				for (int i = 0; i < providedRanges.size(); i++) {
					ArrayRange providedRange = providedRanges.get(i);
					if (providedRange.getLowerBound() == 0) {
						error("Array indices start at 1", providedRange,
								Aadl2Package.eINSTANCE.getArrayRange_LowerBound(), ARRAY_LOWER_BOUND_IS_ZERO);
					}
					// If the upper is zero, then we have an index. Otherwise, we have a range.
					if (providedRange.getUpperBound() != 0) {
						if (providedRange.getLowerBound() > providedRange.getUpperBound()) {
							error("Range lower bound is greater than upper bound", providedRange, null,
									ARRAY_RANGE_UPPER_LESS_THAN_LOWER);
						}
						if (EcoreUtil2.getContainerOfType(pathElement, ReferenceValue.class) != null) {
							warning(providedRange, "Array ranges in reference values are not property instantiated");
						}
					}
					ArrayDimension requiredDimension = requiredDimensions.get(i);
					if (requiredDimension.getSize() == null) {
						error(providedRange, "'" + name + "' does not have an array size");
					} else {
						ArraySizeProperty sizeProperty = requiredDimension.getSize().getSizeProperty();
						OptionalLong size = OptionalLong.empty();
						/*
						 * If the size property is null, then an integer literal was specified for the size.
						 * If the size property is not null, but is a proxy, then the property could not be resolved.
						 */
						if (sizeProperty == null) {
							size = OptionalLong.of(requiredDimension.getSize().getSize());
						} else if (!sizeProperty.eIsProxy()) {
							PropertyExpression constantValue = ((PropertyConstant) sizeProperty).getConstantValue();
							if (constantValue instanceof IntegerLiteral) {
								size = OptionalLong.of(((IntegerLiteral) constantValue).getValue());
							}
						}
						size.ifPresent(requiredSize -> {
							// If the upper is zero, then we have an index. Otherwise, we have a range.
							if (providedRange.getUpperBound() == 0) {
								long index = providedRange.getLowerBound();
								if (index > requiredSize) {
									error("Index is greater than array size " + requiredSize, providedRange,
											Aadl2Package.eINSTANCE.getArrayRange_LowerBound(),
											ARRAY_INDEX_GREATER_THAN_MAXIMUM, Long.toString(requiredSize));
								}
							} else if (providedRange.getUpperBound() > requiredSize) {
								error("Upper bound is greater than array size " + requiredSize, providedRange,
										Aadl2Package.eINSTANCE.getArrayRange_UpperBound(),
										ARRAY_RANGE_UPPER_GREATER_THAN_MAXIMUM, Long.toString(requiredSize));
							}
						});
					}
				}
			}
		} else {
			error(providedRanges.get(0), "'" + name + "' is not an array");
		}
	}

	@Check
	public void checkPropertyReferenceAppliesTo(NamedValue namedValue) {
		AbstractNamedValue anv = namedValue.getNamedValue();
		PropertyAssociation association = EcoreUtil2.getContainerOfType(namedValue, PropertyAssociation.class);
		if (!anv.eIsProxy() && anv instanceof Property && association != null) {
			Property referencedProperty = (Property) anv;
			List<ContainedNamedElement> appliesTo = association.getAppliesTos();
			if (appliesTo.isEmpty()) {
				NamedElement holder = EcoreUtil2.getContainerOfType(association, NamedElement.class);
				if (holder != null && !holder.acceptsProperty(referencedProperty)) {
					error("Property " + referencedProperty.getQualifiedName() + " does not apply to "
							+ holder.getName());
				}
			} else {
				for (ContainedNamedElement cna : appliesTo) {
					List<ContainmentPathElement> path = cna.getContainmentPathElements();
					if (!path.isEmpty()) {
						NamedElement holder = path.get(path.size() - 1).getNamedElement();
						if (!holder.eIsProxy() && !holder.acceptsProperty(referencedProperty)) {
							error("Property " + referencedProperty.getQualifiedName() + " does not apply to "
									+ unparseAppliesTo(cna));
						}
					}
				}
			}
		}
	}

	public void checkContainedProperties(PropertyAssociation pa) {

		List<ModalPropertyValue> mpvs = pa.getOwnedValues();
		if (null == mpvs || mpvs.isEmpty()) {
			return;
		}

		List<ContainedNamedElement> appliesTo = pa.getAppliesTos();
		if (null == appliesTo || appliesTo.size() != 1) {
			return;
		}
		List<Mode> masterModes = getMasterModes(appliesTo.get(0));
		if (null == masterModes || masterModes.isEmpty()) {
			return;
		}

		List<Mode> modesWithProperty = new ArrayList<Mode>();

		ModalPropertyValue lastModalPropertyValue = mpvs.get(mpvs.size() - 1);
		List<Mode> lastInModes = lastModalPropertyValue.getInModes();
		if (null == lastInModes || lastInModes.isEmpty()) {
			modesWithProperty.addAll(masterModes);
		} else {
			for (ModalPropertyValue mpv : mpvs) {
				List<Mode> inModes = mpv.getInModes();
				if (null != inModes && !inModes.isEmpty()) {
					modesWithProperty.removeAll(inModes);
					modesWithProperty.addAll(inModes);
				}
			}
		}

		for (Mode masterMode : masterModes) {
			if (!modesWithProperty.contains(masterMode)) {
				warning(pa, "Value not set for mode " + masterMode.getName() + " for property "
						+ pa.getProperty().getQualifiedName());
			}
		}
	}

	public List<Mode> getMasterModes(ContainedNamedElement cne) {
		List<Mode> masterModes = new ArrayList<Mode>();
		ComponentClassifier componentClassifier = null;
		Subcomponent lastSubcomponent = getLastSubcomponent(cne);

		if (null == lastSubcomponent) {
			Classifier classifier = cne.getContainingClassifier();
			if (classifier instanceof ComponentClassifier) {
				componentClassifier = (ComponentClassifier) classifier;
			}
		} else {
			componentClassifier = lastSubcomponent.getAllClassifier();
			if (null != componentClassifier) {
				masterModes = componentClassifier.getAllModes();
			}
		}

		if (null != componentClassifier) {
			masterModes = componentClassifier.getAllModes();
		}

		return masterModes;
	}

	public Subcomponent getLastSubcomponent(ContainedNamedElement cne) {
		Subcomponent subComponent = null;
		List<ContainmentPathElement> cpes = cne.getContainmentPathElements();
		for (int i = cpes.size() - 1; i > -1; i--) {
			ContainmentPathElement cpe = cpes.get(i);
			if (cpe.getNamedElement() instanceof Subcomponent) {
				subComponent = (Subcomponent) cpe.getNamedElement();
				return subComponent;
			}
		}
		return subComponent;
	}

	public void checkModalAppliesTo(PropertyAssociation pa) {
		boolean error = false;
		List<ContainedNamedElement> appliesTo = pa.getAppliesTos();
		if (null != appliesTo && appliesTo.size() > 1) {
			List<ModalPropertyValue> mpvs = pa.getOwnedValues();
			if (null != mpvs && mpvs.size() > 0) {
				for (ModalPropertyValue mpv : mpvs) {
					List<Mode> inModes = mpv.getInModes();
					if (null != inModes && inModes.size() > 0) {
						error = true;
						break;
					}
				}
			}
		}
		if (error) {
			error(pa,
					"If property value is assigned to a mode there can only be one element in the applies to statement.");
		}

	}

	protected void checkPropertyMissingModes(PropertyAssociation pa) {

		if (null != pa.getAppliesTos() && !pa.getAppliesTos().isEmpty()) {
			return;
		}
		Classifier classifier = pa.getContainingClassifier();
		List<Mode> ownedModes = null;
		if (classifier instanceof ComponentClassifier) {
			ComponentClassifier componentClassifier = (ComponentClassifier) classifier;
			ownedModes = componentClassifier.getOwnedModes();
		}
		if (null == ownedModes) {
			return;
		}
		List<ModalPropertyValue> modalPropertyValues = pa.getOwnedValues();
		if (modalPropertyValues == null || modalPropertyValues.isEmpty()) {
			return;
		}
		ModalPropertyValue lastMpv = modalPropertyValues.get(modalPropertyValues.size() - 1);
		if (lastMpv.getInModes() == null || lastMpv.getInModes().isEmpty()) {
			return;
		}
		List<Mode> allInModes = new ArrayList<Mode>();
		for (ModalPropertyValue mpv : modalPropertyValues) {
			allInModes.addAll(mpv.getInModes());
		}
		for (Mode ownedMode : ownedModes) {
			if (!allInModes.contains(ownedMode)) {
				warning(pa, "Missing value assigned for Mode " + ownedMode.getName());
			}
		}
	}

	public void checkSubcomponentMissingModeValues(Subcomponent subcomponent) {

		ComponentClassifier subcompClassifier = subcomponent.getAllClassifier();
		List<Mode> allModes = new ArrayList<Mode>();
		List<PropertyAssociation> ownedPropertyAssociations = new ArrayList<PropertyAssociation>();
		List<Property> ownedProperties = new ArrayList<Property>();

		if (subcompClassifier != null) {
			allModes = subcompClassifier.getAllModes();
			ownedPropertyAssociations = subcomponent.getOwnedPropertyAssociations();
			ownedProperties = new ArrayList<Property>();
		}
		for (PropertyAssociation ownedPropertyAssociation : ownedPropertyAssociations) {
			ownedProperties.add(ownedPropertyAssociation.getProperty());
		}

		Map<Property, List<Mode>> propModesMap = buildPropertySetForModeMap(allModes, ownedPropertyAssociations);

		for (PropertyAssociation ownedPropertyAssociation : ownedPropertyAssociations) {
			for (Property keyProperty : propModesMap.keySet()) {
				if (keyProperty.equals(ownedPropertyAssociation.getProperty())) {
					List<Mode> mappedModes = propModesMap.get(keyProperty);
					for (Mode nextMode : allModes) {
						if (!mappedModes.contains(nextMode)) {
							warning(ownedPropertyAssociation, "Value not set for mode " + nextMode.getName()
									+ " for property " + keyProperty.getQualifiedName());
						}
					}
				}
			}
		}
	}

	public void checkInheritedMissingModes(Classifier classifier) {
		List<Mode> allModes = new ArrayList<Mode>();
		List<PropertyAssociation> propertyAssociations = new ArrayList<PropertyAssociation>();
		List<PropertyAssociation> ownedPropertyAssociations = new ArrayList<PropertyAssociation>();
		List<Property> ownedProperties = new ArrayList<Property>();
		Element warningTarget = null;
		if (classifier instanceof ComponentImplementation) {
			ComponentImplementation componentImpl = (ComponentImplementation) classifier;
			if (null == componentImpl.getExtended()) {
				return;
			}
			allModes = componentImpl.getAllModes();
			propertyAssociations = componentImpl.getAllPropertyAssociations();
			ownedPropertyAssociations = componentImpl.getOwnedPropertyAssociations();
			ownedProperties = new ArrayList<Property>();
			warningTarget = componentImpl.getOwnedExtension();
		} else if (classifier instanceof ComponentType) {
			ComponentType componentType = (ComponentType) classifier;
			if (null == componentType.getExtended()) {
				return;
			}
			allModes = componentType.getAllModes();
			propertyAssociations = componentType.getAllPropertyAssociations();
			ownedPropertyAssociations = componentType.getOwnedPropertyAssociations();
			ownedProperties = new ArrayList<Property>();
			warningTarget = componentType.getOwnedExtension();
		}
		for (PropertyAssociation ownedPropertyAssociation : ownedPropertyAssociations) {
			ownedProperties.add(ownedPropertyAssociation.getProperty());
		}
		Map<Property, List<Mode>> propModesMap = buildPropertySetForModeMap(allModes, propertyAssociations);
		for (Property keyProperty : propModesMap.keySet()) {
			if (ownedProperties.contains(keyProperty)) {
				continue;
			}

			List<Mode> mappedModes = propModesMap.get(keyProperty);
			for (Mode nextMode : allModes) {
				if (!mappedModes.contains(nextMode)) {
					warning(warningTarget, "Value not set for mode " + nextMode.getName() + " for property "
							+ keyProperty.getQualifiedName());
				}
			}
		}
	}

	protected Map<Property, List<Mode>> buildPropertySetForModeMap(List<Mode> modes,
			List<PropertyAssociation> propertyAssociations) {
		Map<Property, List<Mode>> propertyModeMap = new HashMap<Property, List<Mode>>();
		for (PropertyAssociation pa : propertyAssociations) {
			if (null != pa.getAppliesTos() && !pa.getAppliesTos().isEmpty()) {
				continue;
			}
			Property property = pa.getProperty();
			List<ModalPropertyValue> modalPropertyValues = pa.getOwnedValues();
			if (modalPropertyValues.size() < 1) {
				continue;
			}
			ModalPropertyValue lastMpv = modalPropertyValues.get(modalPropertyValues.size() - 1);
			List<Mode> propertyModes = new ArrayList<Mode>();
			if (lastMpv.getInModes() == null || lastMpv.getInModes().isEmpty()) {
				propertyModes.addAll(modes);
				propertyModeMap.put(property, propertyModes);
			} else {
				for (ModalPropertyValue mpv : modalPropertyValues) {
					List<Mode> inModes = mpv.getInModes();
					for (Mode mode : modes) {
						if (inModes.contains(mode)) {
							propertyModes.add(mode);
						}
					}
				}
				if (propertyModeMap.containsKey(property)) {
					List<Mode> existingList = propertyModeMap.get(property);
					existingList.removeAll(propertyModes);
					propertyModes.addAll(existingList);
				}
				propertyModeMap.put(property, propertyModes);
			}
		}
		return propertyModeMap;
	}

	protected void checkPropertyAssociationModalBindings(PropertyAssociation pa) {
		List<ModalPropertyValue> modalPropertyValues = pa.getOwnedValues();
		Iterator<ModalPropertyValue> mpvIt = modalPropertyValues.iterator();
		while (mpvIt.hasNext()) {
			ModalPropertyValue mpv = mpvIt.next();
			if (mpvIt.hasNext()) {
				List<Mode> modes = mpv.getInModes();
				if (null == modes || modes.isEmpty()) {
					error(mpv, "Missing 'in modes'");
				}
			}
		}

		for (ModalPropertyValue mpv1 : modalPropertyValues) {
			if (null == mpv1) {
				continue;
			}
			List<Mode> inModes1 = mpv1.getInModes();
			for (ModalPropertyValue mpv2 : modalPropertyValues) {
				if (null == mpv2) {
					continue;
				}
				List<Mode> inModes2 = mpv2.getInModes();
				if (mpv1 != mpv2) {
					for (Mode inMode1 : inModes1) {
						if (null == inMode1) {
							continue;
						}
						for (Mode inMode2 : inModes2) {
							if (inMode1.equals(inMode2)) {
								error(mpv2, "Assignment to duplicate modes");
							}
						}
					}
				}
			}
		}
	}

	protected void checkDuplicateFieldAssignment(RecordValue recordValue) {
		EList<BasicPropertyAssociation> ownedValues = recordValue.getOwnedFieldValues();

		for (BasicPropertyAssociation association : ownedValues) {
			for (BasicPropertyAssociation association2 : ownedValues) {
				if (!(association.equals(association2))
						&& association.getProperty().equals(association2.getProperty())) {
					error(association, "Duplicate assignment of record value");
				}
			}
		}
	}

	public void checkPropertySetElementReferenceForPackageProperties(NamedElement pse, Element context) {
		if (Aadl2Util.isNull(pse)) {
			return;
		}
		PropertySet referenceNS = (PropertySet) AadlUtil.getContainingTopLevelNamespace(pse);
		PackageSection containingPackageSection = EcoreUtil2.getContainerOfType(context, PackageSection.class);
		if (containingPackageSection == null) {
			AadlPackage aadlPackage = EcoreUtil2.getContainerOfType(context, AadlPackage.class);
			EList<ModelUnit> importedPropertySets = null;
			PackageSection packageSection = aadlPackage.getPublicSection();
			if (packageSection == null) {
				packageSection = aadlPackage.getPrivateSection();
			}
			if (packageSection != null) {
				importedPropertySets = packageSection.getImportedUnits();
				for (ModelUnit importedPropertySet : importedPropertySets) {
					if (importedPropertySet instanceof PropertySet && !importedPropertySet.eIsProxy()
							&& (importedPropertySet == referenceNS || (referenceNS.getQualifiedName()
									.equalsIgnoreCase(importedPropertySet.getQualifiedName())))) {
						return;
					}
				}
			}
			if (packageSection != null) {
				error("The referenced property set '" + referenceNS.getName() + "' of "
						+ (pse instanceof Property ? "property '"
								: (pse instanceof PropertyType ? "property type '" : "property constant '"))
						+ pse.getName() + "' is not listed in a with clause.", context, null, MISSING_WITH,
						referenceNS.getName(), EcoreUtil.getURI(referenceNS).toString(),
						EcoreUtil.getURI(packageSection).toString());
			} else {
				error("The referenced property set '" + referenceNS.getName() + "' of "
						+ (pse instanceof Property ? "property '"
								: (pse instanceof PropertyType ? "property type '" : "property constant '"))
						+ pse.getName() + "' is not listed in a with clause.", context, null);
			}
		}
	}

	protected void checkPropertyAssociation(PropertyAssociation pa) {
		// type check value against type
		Property pdef = pa.getProperty();

		checkPropertySetElementReferenceForPackageProperties(pdef, pa);

		checkPropertySetElementReference(pdef, pa);
		if (Aadl2Util.isNull(pdef)) {
			return;
		}

		PropertyType pt = pdef.getPropertyType();
		if (Aadl2Util.isNull(pt)) {
			return;
		}

		EList<ModalPropertyValue> pvl = pa.getOwnedValues();
		for (ModalPropertyValue modalPropertyValue : pvl) {
			typeCheckPropertyValues(pt, modalPropertyValue.getOwnedValue(), modalPropertyValue.getOwnedValue(),
					pdef.getQualifiedName(), 0);
		}
		checkAssociationAppliesTo(pa);
		checkInBinding(pa);
		if (pa.getProperty() != null) {
			if ("Byte_Count".equalsIgnoreCase(pa.getProperty().getName())) {
				boolean offerQuickFix = true;
				for (ModalPropertyValue modalPropertyValue : pvl) {
					PropertyExpression pe = modalPropertyValue.getOwnedValue();
					if (!(pe instanceof NumberValue)) {
						offerQuickFix = false;
						break;
					}
				}

				if (offerQuickFix) {
					warning("Byte_Count is deprecated. Please use Memory_Size.", pa, null, BYTE_COUNT_DEPRECATED);
				} else {
					warning(pa, "Byte_Count is deprecated. Please use Memory_Size.");
				}
			} else
//			if ( "SEI::Data_Rate".equalsIgnoreCase(pa.getProperty().getQualifiedName())) {
//				warning("SEI::Data_Rate is deprecated. Please use SEI::Message_Rate.", pa, null, SEI_DATA_RATE_DEPRECATED);
//			} else
			if ("Source_Code_Size".equalsIgnoreCase(pa.getProperty().getName())) {
				warning("Source_Code_Size is deprecated. Please use Code_Size.", pa, null, SOURCE_CODE_SIZE_DEPRECATED);
			} else if ("Source_Data_Size".equalsIgnoreCase(pa.getProperty().getName())) {
				warning("Source_Data_Size is deprecated. Please use Data_Size.", pa, null, SOURCE_DATA_SIZE_DEPRECATED);
			} else if ("Source_Heap_Size".equalsIgnoreCase(pa.getProperty().getName())) {
				warning("Source_Heap_Size is deprecated. Please use Heap_Size.", pa, null, SOURCE_HEAP_SIZE_DEPRECATED);
			} else if ("Source_Stack_Size".equalsIgnoreCase(pa.getProperty().getName())) {
				warning("Source_Stack_Size is deprecated. Please use Stack_Size.", pa, null,
						SOURCE_STACK_SIZE_DEPRECATED);
			} else if ("Data_Volume".equalsIgnoreCase(pa.getProperty().getName())) {
				warning("Data_Volume is deprecated. Please use Data_Rate.", pa, null, DATA_VOLUME_DEPRECATED);
			}
		}
		checkConstantProperty(pa);
	}

	protected void checkConstantProperty(PropertyAssociation assoc) {
		Property property = assoc.getProperty();
		if (!property.eIsProxy()) {
			EList<ContainedNamedElement> appliesTos = assoc.getAppliesTos();

			if (appliesTos == null || appliesTos.isEmpty()) {
				NamedElement holder = (NamedElement) assoc.getOwner();
				if (holder.acceptsProperty(property)) {
					checkOverridingConstant(holder, assoc);
				}
			} else {
				for (ContainedNamedElement cne : assoc.getAppliesTos()) {
					if (cne.getContainmentPathElements().size() == 1) {
						ContainmentPathElement cpe = cne.getContainmentPathElements().get(0);
						NamedElement ne = cpe.getNamedElement();
						if (!ne.eIsProxy() && ne.acceptsProperty(property)) {
							checkOverridingConstant(ne, assoc);
						}
					}
				}
			}
		}
	}

	protected void checkOverridingConstant(NamedElement holder, PropertyAssociation assoc) {
		Property prop = assoc.getProperty();
		PropertyAcc acc = holder.getPropertyValue(prop, true);
		List<PropertyAssociation> pas = acc.getAssociations();

		if (pas.size() > 1) {
			// when checking a local pa that is overwritten by a local contained pa we need
			// to skip the first 2 list elements otherwise just the first element, which
			// is the currently checked pa
			ListIterator<PropertyAssociation> iter = pas.listIterator(1);
			if (iter.hasNext()) {
				PropertyAssociation pa = iter.next();
				if (pa != assoc) {
					iter.previous();
				}
			}
			while (iter.hasNext()) {
				PropertyAssociation pa = iter.next();
				if (pa.isConstant()) {
					error(assoc, "Property association overrides constant property value from "
							+ pa.getContainingClassifier().getQualifiedName());
				}
			}
		}
	}

	protected void checkInBinding(final PropertyAssociation pa) {
		for (Classifier c : pa.getInBindings()) {
			checkClassifierReferenceInWith(c, pa);
		}
	}

	/**
	 * Check constraints that property applies to the element it is associated
	 * with per Section 10.3:
	 *
	 * <blockquote>The property named by a property association must list the
	 * category of the component type, component implementation, subcomponent,
	 * feature, connection, flow, or mode the property association is declared
	 * for in its Property_Owner_Category list. </blockquote>
	 */
	private void checkAssociationAppliesTo(final PropertyAssociation pa) {
		final Property pn = pa.getProperty();
		final EList<ContainedNamedElement> appliesTo = pa.getAppliesTos();
		if (appliesTo == null || appliesTo.size() == 0) {
			Element element = pa.getOwner();
			if (element instanceof NamedElement) {
				final boolean applies = ((NamedElement) element).acceptsProperty(pn);
				if (!applies) {
					error(pa, "Property " + pa.getProperty().getQualifiedName() + " does not apply to "
							+ ((NamedElement) element).getName());
					// error(pa,
					// "Property " + pa.getQualifiedName() +
					// " does not apply to " + element.eClass().getName());
				}
			}
		} else {
			for (ContainedNamedElement cna : appliesTo) {
				EList<ContainmentPathElement> path = cna.getContainmentPathElements();
				if (!path.isEmpty()) {
					// only the last value is interesting to us
					final ContainmentPathElement ph = path.get(path.size() - 1);
					if (!Aadl2Util.isNull(ph.getNamedElement())) {
						final boolean applies = ph.getNamedElement().acceptsProperty(pn);
						if (!applies) {
							error(pa, "Property " + pa.getProperty().getQualifiedName() + " does not apply to "
									+ unparseAppliesTo(cna));
						}
					}
				}
			}
		}
	}

	private static String unparseAppliesTo(final ContainedNamedElement cna) {
		final StringBuffer sb = new StringBuffer();
		EList<ContainmentPathElement> path = cna.getContainmentPathElements();
		for (final Iterator<ContainmentPathElement> it = path.iterator(); it.hasNext();) {
			final ContainmentPathElement pc = it.next();
			sb.append(pc.getNamedElement().getName());
			if (it.hasNext()) {
				sb.append(".");
			}
		}
		return sb.toString();
	}

	/**
	 * checks and report mismatch in type of value and type
	 *
	 * @param pt:
	 *            PropertyType or unresolved proxy or null
	 * @param pv:
	 *            PropertyExpression or null
	 * @since 2.0
	 */
	protected void typeCheckPropertyValues(PropertyType pt, PropertyExpression pv, Element holder, String defName,
			int depth) {
		typeCheckPropertyValues(pt, pv, "", holder, defName, depth);
	}

	/**
	 * checks and report mismatch in type of value and type
	 *
	 * @param pt:
	 *            PropertyType or unresolved proxy or null
	 * @param pv:
	 *            PropertyExpression or null
	 * @param prefix:
	 *            String prefix to error message used for lists
	 * @since 2.0
	 */
	protected void typeCheckPropertyValues(PropertyType pt, PropertyExpression pv, String prefix, Element holder,
			String defName, int depth) {

		if (Aadl2Util.isNull(pt) || pv == null || holder == null) {
			return;
		}
		if (depth > 50) {
			error(holder, "Cyclic value discovered for '" + defName + "'");
			return;
		}
		depth++;
		String msg = " to property '" + defName + "' of type '" + pt.eClass().getName() + "'";
		if (!prefix.isEmpty() && !prefix.startsWith(" ")) {
			prefix = prefix + " ";
		}
		if (pv instanceof ListValue) {
			if (pt instanceof ListType) {
				typeMatchListElements(((ListType) pt).getElementType(), ((ListValue) pv).getOwnedListElements(), holder,
						defName, depth);
			} else {
				error(holder, prefix + "Assigning a list of values" + msg);
			}
		} else if (pv instanceof Operation || pv instanceof BooleanLiteral) {
			if (!(pt instanceof AadlBoolean)) {
				error(holder, prefix + "Assigning a Boolean value" + msg);
			}
		} else if (pv instanceof StringLiteral) {
			if (!(pt instanceof AadlString)) {
				error(prefix + "Assigning String value" + msg, holder, null,
						ValidationMessageAcceptor.INSIGNIFICANT_INDEX, Diagnostic.LINKING_DIAGNOSTIC);

			}
		} else if (pv instanceof EnumerationLiteral
				|| (pv instanceof NamedValue && ((NamedValue) pv).getNamedValue() instanceof EnumerationLiteral)) {
			if (!(pt instanceof EnumerationType)) {
				error(holder, prefix + "Assigning Enumeration literal" + msg);
			}
		} else if (pv instanceof UnitLiteral
				|| (pv instanceof NamedValue && ((NamedValue) pv).getNamedValue() instanceof UnitLiteral)) {
			if (!(pt instanceof UnitsType)) {
				error(holder, prefix + "Assigning Unit literal" + msg);
			}
		} else if (pv instanceof IntegerLiteral) {
			if (!(pt instanceof AadlInteger)) {
				error(holder, prefix + "Assigning Integer value" + msg);
			} else if (checkUnits((AadlInteger) pt, (IntegerLiteral) pv, holder)) {
				checkInRange((AadlInteger) pt, (IntegerLiteral) pv);
			}
		} else if (pv instanceof RealLiteral) {
			if (!(pt instanceof AadlReal)) {
				error(holder, prefix + "Assigning Real value" + msg);
			} else if (checkUnits((AadlReal) pt, (RealLiteral) pv, holder)) {
				checkInRange((AadlReal) pt, (RealLiteral) pv);
			}
		} else if (pv instanceof RangeValue) {
			if (!(pt instanceof RangeType)) {
				error(holder, prefix + "Assigning Range value" + msg);
			} else {
				typeCheckPropertyValues(((RangeType) pt).getNumberType(), ((RangeValue) pv).getMinimumValue(), holder,
						defName, depth);
				typeCheckPropertyValues(((RangeType) pt).getNumberType(), ((RangeValue) pv).getMaximumValue(), holder,
						defName, depth);
				typeCheckPropertyValues(((RangeType) pt).getNumberType(), ((RangeValue) pv).getDeltaValue(), holder,
						defName, depth);
			}
		} else if (pv instanceof ClassifierValue) {
			if (!(pt instanceof ClassifierType)) {
				error(holder, prefix + "Assigning incorrect Classifier value" + msg);
				return;
			}
			ClassifierValue cv = (ClassifierValue) pv;
			ClassifierType ct = (ClassifierType) pt;

			if (ct.getClassifierReferences().isEmpty()) {
				return;
			}
			for (MetaclassReference mcri : ct.getClassifierReferences()) {
				if (mcri.getMetaclass() != null && mcri.getMetaclass().isSuperTypeOf(cv.getClassifier().eClass())) {
					return;
				}
			}
			error(holder, prefix + "Assigning classifier value with incorrect Classifier" + msg);
		} else if (pv instanceof RecordValue) {
			if (!(pt instanceof RecordType)) {
				error(holder, prefix + "Assigning Record value" + msg);
			} else {
				typeMatchRecordFields(((RecordValue) pv).getOwnedFieldValues(), holder, defName, depth);
			}
		} else if (pv instanceof ReferenceValue) {
			if (!(pt instanceof ReferenceType)) {
				error(holder, prefix + "Assigning incorrect reference value" + msg);
			} else {
				ReferenceType ptrt = (ReferenceType) pt;
				if (ptrt.getNamedElementReferences().isEmpty()) {
					return;
				}
				ReferenceValue pvrv = (ReferenceValue) pv;
				EList<ContainmentPathElement> cpes = pvrv.getContainmentPathElements();
				if (!cpes.isEmpty()) {
					NamedElement ne = cpes.get(cpes.size() - 1).getNamedElement();
					for (MetaclassReference mcri : ptrt.getNamedElementReferences()) {
						if (mcri.getMetaclass().isSuperTypeOf(ne.eClass())) {
							return;
						}
					}
					error(holder, prefix + "Assigning reference value with incorrect Named Element class" + msg);
				}
			}
		} else if (pv instanceof NamedValue) {
			AbstractNamedValue nv = ((NamedValue) pv).getNamedValue();
			if (nv instanceof PropertyConstant) {
				final PropertyConstant propertyConstant = (PropertyConstant) nv;
				final PropertyType pct = propertyConstant.getPropertyType();
				if (!Aadl2Util.isNull(pct) && !Aadl2Util.arePropertyTypesEqual(pt, pct)) {
					final String expected = getTypeName(pt);
					final String actual = getTypeName(pct);
					if (actual != null) {
						if (expected != null) {
							error(holder, "Property value of type " + actual + "; expected type " + expected);
						} else {
							error(holder, "Propery value of type " + actual + " does not match expected type");
						}
					} else {
						if (expected != null) {
							error(holder, "Property value is not of expected type " + expected);
						} else {
							error(holder, "Propery value is not of expected type");
						}
					}
				} else {
					// Issue 2222: is this still really necessary?
					typeCheckPropertyValues(pt, propertyConstant.getConstantValue(), holder, defName, depth);
				}
			} else if (nv instanceof Property) {
				PropertyType pvt = ((Property) nv).getPropertyType();
				if (!Aadl2Util.isNull(pvt)) {
					if (pvt.eClass() != pt.eClass() || !Aadl2Util.arePropertyTypesEqual(pt, pvt)) {
						final String expected = getTypeName(pt);
						final String actual = getTypeName(pvt);
						if (actual != null) {
							if (expected != null) {
								error(holder, "Property value of type " + actual + "; expected type " + expected);
							} else {
								error(holder, "Propery value of type " + actual + " does not match expected type");
							}
						} else {
							if (expected != null) {
								error(holder, "Property value is not of expected type " + expected);
							} else {
								error(holder, "Propery value is not of expected type");
							}
						}
					}
				}
			} else {
				error(holder, "Enum/Unit literal validation should have happened before");
			}
		}
	}

	/**
	 * @since 2.0
	 */
	protected void typeMatchListElements(PropertyType pt, EList<PropertyExpression> pel, Element holder,
			String defName, int depth) {
		for (PropertyExpression propertyExpression : pel) {
			typeCheckPropertyValues(pt, propertyExpression, "list element", propertyExpression, defName, depth);
		}
	}

	/**
	 * @since 2.0
	 */
	protected void typeMatchRecordFields(EList<BasicPropertyAssociation> rfl, Element holder, String defName,
			int depth) {
		for (BasicPropertyAssociation field : rfl) {
			if (field.getProperty() != null) {
				typeCheckPropertyValues(field.getProperty().getPropertyType(), field.getValue(), field.getValue(),
						defName, depth);
			}
		}
	}

	/**
	 * Returns true if the units are valid. Returns false if an error was reported.
	 */
	protected boolean checkUnits(NumberType nt, NumberValue nv, Element holder) {
		UnitsType ut = nt.getUnitsType();
		UnitLiteral ul = nv.getUnit();
		if (Aadl2Util.isNull(ut) && Aadl2Util.isNull(ul)) {
			return true;
		}
		if (ul == null) {
			boolean doQuickFix = false;
			EObject container = nv;
			while (null != container) {
				if (container.equals(holder)) {
					doQuickFix = true;
					break;
				}
				container = container.eContainer();
			}

			if (doQuickFix) {
				EList<EnumerationLiteral> allUTElements = ut.getOwnedLiterals();
				String[] unitNamesAndURIs = new String[allUTElements.size() * 2];
				int i = 0;
				for (EnumerationLiteral elem : allUTElements) {
					unitNamesAndURIs[i] = elem.getName();
					i++;
					unitNamesAndURIs[i] = EcoreUtil.getURI(elem).toString();
					i++;
				}
				error("Number value is missing a unit", holder, null, MISSING_NUMBERVALUE_UNITS, unitNamesAndURIs);
				return false;
			} else {
				error(holder, "Number value is missing a unit");
				return false;
			}
		} else if (!ut.getOwnedLiterals().contains(ul)) {
			error(holder, "Unit '" + ul.getName() + "'of number value is not of Units type " + ut.getQualifiedName());
			return false;
		} else {
			return true;
		}
	}

	private void checkInRange(NumberType type, NumberValue value) {
		NumericRange range = type.getRange();
		if (range != null) {
			PropertyExpression lowerExpression;
			try {
				MpvProxy modalProxy = range.getLowerBound().evaluate(null, 0).first();
				lowerExpression = modalProxy == null ? null : modalProxy.getValue();
			} catch (InvalidModelException e) {
				lowerExpression = null;
			}
			PropertyExpression upperExpression;
			try {
				MpvProxy modalProxy = range.getUpperBound().evaluate(null, 0).first();
				upperExpression = modalProxy == null ? null : modalProxy.getValue();
			} catch (InvalidModelException e) {
				upperExpression = null;
			}
			if (lowerExpression instanceof NumberValue && upperExpression instanceof NumberValue) {
				NumberValue lower = (NumberValue) lowerExpression;
				NumberValue upper = (NumberValue) upperExpression;
				double valueScaled = value.getScaledValue();
				if (valueScaled < lower.getScaledValue() || valueScaled > upper.getScaledValue()) {
					error(value, "Value must be between " + lower + " and " + upper);
				}
			}
		}
	}

	public void checkClassifierReferenceInWith(Classifier cl, Element context) {
		if (Aadl2Util.isNull(cl)) {
			return;
		}
		Namespace contextNS = AadlUtil.getContainingTopLevelNamespace(context);
		PackageSection referenceNS = (PackageSection) AadlUtil.getContainingTopLevelNamespace(cl);
		if (contextNS != referenceNS) {
			AadlPackage referencePackage = AadlUtil.getContainingPackage(referenceNS);
			if (!AadlUtil.isImportedPackage(referencePackage, contextNS)) {
				error("The referenced package '" + referencePackage.getName() + "' of classifier '" + cl.getName()
						+ "' is not listed in a with clause.", context, null, MISSING_WITH, referencePackage.getName(),
						EcoreUtil.getURI(referencePackage).toString(), EcoreUtil.getURI(contextNS).toString());
			}
		}
	}

	public void checkPropertySetElementReference(NamedElement pse, Element context) {
		if (Aadl2Util.isNull(pse)) {
			return;
		}
		Namespace contextNS = AadlUtil.getContainingTopLevelNamespace(context);
		PropertySet referenceNS = (PropertySet) AadlUtil.getContainingTopLevelNamespace(pse);
		if (contextNS != referenceNS) {
			if (!AadlUtil.isImportedPropertySet(referenceNS, contextNS)) {
				error("The referenced property set '" + referenceNS.getName() + "' of "
						+ (pse instanceof Property ? "property '"
								: (pse instanceof PropertyType ? "property type '" : "property constant '"))
						+ pse.getName() + "' is not listed in a with clause.", context, null, MISSING_WITH,
						referenceNS.getName(), EcoreUtil.getURI(referenceNS).toString(),
						EcoreUtil.getURI(contextNS).toString());
			}
		}
	}

	private void typeCheckContainmentPathElement(ContainmentPathElement pathElement) {
		if (pathElement.getOwner() instanceof ContainmentPathElement
				&& (pathElement.getNamedElement() instanceof InternalFeature
						|| pathElement.getNamedElement() instanceof ProcessorFeature)) {
			error(StringExtensions
					.toFirstUpper(getEClassDisplayNameWithIndefiniteArticle(pathElement.getNamedElement().eClass()))
					+ " is not visible outside of its component implementation or extending implementations.",
					pathElement, Aadl2Package.eINSTANCE.getContainmentPathElement_NamedElement());
		}
	}

	protected static String getEClassDisplayNameWithIndefiniteArticle(EClass eClass) {
		StringBuilder displayName = new StringBuilder(eClass.getName());
		for (int i = displayName.length() - 1; i > 0; i--) {
			if (Character.isUpperCase(displayName.charAt(i))) {
				displayName.insert(i, ' ');
			}
		}
		if ("AEIOU".indexOf(displayName.charAt(0)) == -1) {
			displayName.insert(0, "a '");
		} else {
			displayName.insert(0, "an '");
		}
		displayName.append('\'');
		return displayName.toString().toLowerCase();
	}

	// helper methods

	@Override
	protected void error(String message, EObject source, EStructuralFeature feature) {
		error(message, source, feature, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null);
	}

	protected void error(EObject source, String message) {
		error(message, source, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null);
	}

	protected void error(String message) {
		error(message, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null);
	}

	@Override
	protected void warning(String message, EObject source, EStructuralFeature feature) {
		warning(message, source, feature, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null);
	}

	protected void warning(EObject source, String message) {
		warning(message, source, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null);
	}

	protected void warning(String message) {
		warning(message, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null);
	}

	protected void info(EObject source, String message) {
		info(message, source, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null);
	}

	protected void info(String message) {
		info(message, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null);
	}

	/**
	 * Get the name of the type, if it has a user-declared name, otherwise returns {@code null}.  The
	 * only special handing is of list types, where if the ultimate element type of the list has a name,
	 * then all the "list of" prefixes are attached.  Otherwise if the ultimate element type has no name
	 * the result is still {@code null}.
	 */
	private String getTypeName(final PropertyType pt) {
		if (pt instanceof ListType) {
			final String elementTypeName = getTypeName(((ListType) pt).getElementType());
			if (elementTypeName != null) {
				return "list of " + elementTypeName;
			} else {
				return null;
			}
		} else {
			if (pt.hasName()) {
				return ((PropertySet) pt.eContainer()).getName() + "::" + pt.getName();
			} else {
				return null;
			}
		}
	}
}