/*******************************************************************************
* Copyright (c) 2008-2011 itemis AG (http://www.itemis.eu) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.emf.mwe2.language.validation;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.mwe2.language.mwe2.AbstractReference;
import org.eclipse.emf.mwe2.language.mwe2.Assignment;
import org.eclipse.emf.mwe2.language.mwe2.Component;
import org.eclipse.emf.mwe2.language.mwe2.DeclaredProperty;
import org.eclipse.emf.mwe2.language.mwe2.Module;
import org.eclipse.emf.mwe2.language.mwe2.Mwe2Package;
import org.eclipse.emf.mwe2.language.mwe2.Referrable;
import org.eclipse.emf.mwe2.language.scoping.FactorySupport;
import org.eclipse.emf.mwe2.language.scoping.Mwe2ScopeProvider;
import org.eclipse.emf.mwe2.runtime.Mandatory;
import org.eclipse.xtext.common.types.JvmAnnotationReference;
import org.eclipse.xtext.common.types.JvmConstructor;
import org.eclipse.xtext.common.types.JvmDeclaredType;
import org.eclipse.xtext.common.types.JvmFeature;
import org.eclipse.xtext.common.types.JvmGenericType;
import org.eclipse.xtext.common.types.JvmIdentifiableElement;
import org.eclipse.xtext.common.types.JvmMember;
import org.eclipse.xtext.common.types.JvmOperation;
import org.eclipse.xtext.common.types.JvmParameterizedTypeReference;
import org.eclipse.xtext.common.types.JvmPrimitiveType;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.common.types.JvmVisibility;
import org.eclipse.xtext.common.types.TypesFactory;
import org.eclipse.xtext.common.types.TypesPackage;
import org.eclipse.xtext.common.types.util.Primitives;
import org.eclipse.xtext.common.types.util.RawSuperTypes;
import org.eclipse.xtext.naming.IQualifiedNameConverter;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.scoping.IScope;
import org.eclipse.xtext.util.Strings;
import org.eclipse.xtext.validation.Check;
import org.eclipse.xtext.validation.ValidationMessageAcceptor;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
/**
* @author Sebastian Zarnekow - Initial contribution and API
*/
@SuppressWarnings("restriction")
public class Mwe2JavaValidator extends AbstractMwe2JavaValidator {
@Inject
private FactorySupport factorySupport;
@Inject
private Mwe2ScopeProvider scopeProvider;
@Inject
private IQualifiedNameConverter qualifiedNameConverter;
@Inject
private RawSuperTypes rawSuperTypes;
@Inject
private Primitives primitives;
public final static String INCOMPATIBLE_ASSIGNMENT = "incompatible_assignment";
@Check
public void checkCompatibility(Assignment assignment) {
JvmIdentifiableElement feature = assignment.getFeature();
if (feature.eIsProxy())
return;
JvmTypeReference left = null;
if (feature instanceof JvmOperation) {
JvmOperation op = (JvmOperation) feature;
left = op.getParameters().get(0).getParameterType();
} else if (feature instanceof DeclaredProperty) {
DeclaredProperty property = (DeclaredProperty) feature;
JvmType propertyType = property.getActualType();
JvmParameterizedTypeReference propertyTypeRef = TypesFactory.eINSTANCE.createJvmParameterizedTypeReference();
propertyTypeRef.setType(propertyType);
left = propertyTypeRef;
} else {
throw new UnsupportedOperationException(
"Can not handle features of type "
+ feature.getClass().getName() + " - " + feature);
}
if (left != null) {
if (assignment.getValue() == null || assignment.getValue().eIsProxy())
return;
JvmType rightType = assignment.getValue().getActualType();
if (rightType == null || rightType.eIsProxy())
return;
JvmType factoryType = factorySupport.findFactoriesCreationType(rightType);
if (factoryType != null) {
rightType = factoryType;
}
if (!isAssignableFrom(left, rightType)) {
error(
"A value of type '" + rightType.getQualifiedName('.')
+ "' can not be assigned to the feature "
+ feature.getIdentifier(),
Mwe2Package.Literals.ASSIGNMENT__VALUE,
ValidationMessageAcceptor.INSIGNIFICANT_INDEX,
INCOMPATIBLE_ASSIGNMENT);
}
}
}
protected boolean isAssignableFrom(JvmTypeReference left, JvmType right) {
JvmType leftRaw = left.getType();
// simplified conformance check
return isAssignableFrom(leftRaw, right);
}
protected boolean isAssignableFrom(JvmType left, JvmType right) {
if (left == right) {
return true;
}
if (left == null || left.eIsProxy()) {
return true;
}
if (right == null || right.eIsProxy()) {
return true;
}
if (rawSuperTypes.collect(right).contains(left)) {
return true;
}
// simplified conformance check
if (right.eClass() == TypesPackage.Literals.JVM_PRIMITIVE_TYPE && left.eClass() != TypesPackage.Literals.JVM_PRIMITIVE_TYPE) {
JvmType wrapper = primitives.getWrapperType((JvmPrimitiveType) right);
boolean result = isAssignableFrom(left, wrapper);
return result;
}
if (left.eClass() == TypesPackage.Literals.JVM_PRIMITIVE_TYPE && right.eClass() != TypesPackage.Literals.JVM_PRIMITIVE_TYPE) {
if (right instanceof JvmDeclaredType) {
JvmType primitive = primitives.getPrimitiveTypeIfWrapper((JvmDeclaredType) right);
if (primitive != null) {
boolean result = isAssignableFrom(left, primitive);
return result;
}
}
}
return false;
}
@Check
public void checkCompatibility(DeclaredProperty property) {
if (property.getType()!=null && property.getDefault()!=null) {
JvmType actualType = property.getDefault().getActualType();
JvmType factoryType = factorySupport.findFactoriesCreationType(actualType);
if (factoryType != null) {
actualType = factoryType;
}
if (!isAssignableFrom(property.getType(), actualType)) {
error(
"A value of type '" + actualType.getQualifiedName('.')
+ "' can not be assigned to a reference of type "
+ property.getType().getQualifiedName('.'),
Mwe2Package.Literals.DECLARED_PROPERTY__DEFAULT,
ValidationMessageAcceptor.INSIGNIFICANT_INDEX,
INCOMPATIBLE_ASSIGNMENT);
}
}
}
public final static String UNUSED_LOCAL = "unused_local_variable";
public final static String DUPLICATE_LOCAL = "duplicate_local_variable";
@Check
public void checkReferables(Module referable) {
TreeIterator<EObject> iterator = referable.eResource().getAllContents();
Set<String> referenced = Sets.newHashSet();
Multimap<String, Referrable> declared = HashMultimap.create();
while (iterator.hasNext()) {
EObject next = iterator.next();
if (next instanceof Referrable) {
String name = ((Referrable) next).getName();
if (name != null) {
declared.put(name, (Referrable) next);
}
} else if (next instanceof AbstractReference) {
referenced.add(((AbstractReference) next).getReferable().getName());
}
if (next instanceof Component) {
Component component = (Component) next;
if (component.isAutoInject()) {
Set<String> featureNames = collectFeatureNames(component);
Set<String> explicitlyAssignedFeature = Sets.newHashSet();
for(Assignment assignment: component.getAssignment()) {
explicitlyAssignedFeature.add(assignment.getFeatureName());
}
featureNames.removeAll(explicitlyAssignedFeature);
featureNames.retainAll(declared.keySet());
referenced.addAll(featureNames);
}
}
}
Multimap<String, Referrable> copy = HashMultimap.create(declared);
copy.keySet().removeAll(referenced);
for (Referrable referrable : copy.values()) {
warning(
"The var '" + referrable.getName() + "' is never read locally.",
referrable,
Mwe2Package.Literals.REFERRABLE__NAME,
ValidationMessageAcceptor.INSIGNIFICANT_INDEX,
UNUSED_LOCAL);
}
for (String name : declared.keySet()) {
Collection<Referrable> collection = declared.get(name);
if (collection.size()>1) {
for (Referrable referrable : collection) {
error(
"Duplicate var '" + referrable.getName() + "'.",
referrable,
Mwe2Package.Literals.REFERRABLE__NAME,
ValidationMessageAcceptor.INSIGNIFICANT_INDEX,
DUPLICATE_LOCAL);
}
}
}
}
public Set<String> collectFeatureNames(Component component) {
Set<String> result = Sets.newHashSet();
IScope scope = scopeProvider.createComponentFeaturesScope(component);
for(IEObjectDescription description: scope.getAllElements()) {
result.add(qualifiedNameConverter.toString(description.getName()));
}
return result;
}
public final static String MISSING_DEFAULT_CONSTRUCTOR = "missing_default_constructor";
@Check
public void checkInstantiable(Component component) {
if (component.getModule() != null)
return;
JvmType actualType = component.getActualType();
if (actualType == null || actualType.eIsProxy())
return;
JvmDeclaredType declaredType = (JvmDeclaredType) actualType;
if (!declaredType.isAbstract() &&
!(declaredType instanceof JvmGenericType && ((JvmGenericType) declaredType).isInterface())) {
for(JvmMember member: declaredType.getMembers()) {
if (member instanceof JvmConstructor) {
if (((JvmConstructor) member).getParameters().isEmpty() && member.getVisibility().equals(JvmVisibility.PUBLIC))
return;
}
}
error(
"'" + declaredType.getQualifiedName('.') + "' does not have a public default constructor.",
component,
Mwe2Package.Literals.REFERRABLE__TYPE,
ValidationMessageAcceptor.INSIGNIFICANT_INDEX,
MISSING_DEFAULT_CONSTRUCTOR);
}
}
public final static String ABSTRACT_OR_INTERFACE = "abstract_or_interface";
@Check
public void checkComponentTypeIsInterfaceOrAbstract(Component component) {
if (component.getModule() != null)
return;
JvmType actualType = component.getActualType();
if (actualType == null || actualType.eIsProxy())
return;
JvmDeclaredType declaredType = (JvmDeclaredType) actualType;
if (declaredType.isAbstract() ||
(declaredType instanceof JvmGenericType && ((JvmGenericType) declaredType).isInterface())) {
error(
"'" + declaredType.getQualifiedName('.') + "' is not instantiable.",
component,
Mwe2Package.Literals.REFERRABLE__TYPE,
ValidationMessageAcceptor.INSIGNIFICANT_INDEX,
ABSTRACT_OR_INTERFACE);
}
}
public final static String MISSING_MANDATORY_FEATURE = "missing_mandatory_feature";
@Check
public void checkManadatoryFeaturesAssigned(Component component) {
Map<String, JvmIdentifiableElement> mandatoryFeatures = collectMandatoryFeatures(component);
if (!mandatoryFeatures.isEmpty()) {
Map<String, Referrable> availableProperties = collectReferablesUpTo(component);
Set<String> assignedFeatures = getAssignedFeatures(availableProperties, component);
mandatoryFeatures.keySet().removeAll(assignedFeatures);
if (!mandatoryFeatures.isEmpty()) {
List<String> missingAssignments = Lists.newArrayList(mandatoryFeatures.keySet());
Collections.sort(missingAssignments);
String concatenated = Strings.concat(", ", missingAssignments);
EStructuralFeature feature = null;
if (component.getType() != null)
feature = Mwe2Package.Literals.REFERRABLE__TYPE;
else if (component.getModule() != null)
feature = Mwe2Package.Literals.COMPONENT__MODULE;
else if (component.getName() != null)
feature = Mwe2Package.Literals.REFERRABLE__NAME;
if (missingAssignments.size() == 1)
error(
"Mandatory feature was not assigned: '" + concatenated + "'.",
component,
feature,
ValidationMessageAcceptor.INSIGNIFICANT_INDEX,
MISSING_MANDATORY_FEATURE);
else
error(
"Mandatory features were not assigned: '" + concatenated + "'.",
component,
feature,
ValidationMessageAcceptor.INSIGNIFICANT_INDEX,
MISSING_MANDATORY_FEATURE);
}
}
}
private Set<String> getAssignedFeatures(
Map<String, Referrable> availableProperties, Component component) {
Set<String> result = Sets.newHashSet();
if (component.isAutoInject()) {
result.addAll(availableProperties.keySet());
}
for(Assignment assignment: component.getAssignment()) {
if (assignment.getFeature() != null && !assignment.getFeature().eIsProxy()) {
JvmIdentifiableElement feature = assignment.getFeature();
if (feature instanceof JvmOperation) {
result.add(Strings.toFirstLower((((JvmOperation)feature).getSimpleName().substring(3))));
} else if (feature instanceof JvmFeature) {
result.add(((JvmFeature) feature).getSimpleName());
} else {
result.add(((DeclaredProperty)feature).getName());
}
}
}
return result;
}
public Map<String, Referrable> collectReferablesUpTo(Component component) {
List<Referrable> result = Lists.newArrayList();
scopeProvider.collectReferablesUpTo(component, true, result);
Map<String, Referrable> indexed = Maps.newHashMap();
for(Referrable referrable: result) {
if (referrable.getName() != null) {
indexed.put(referrable.getName(), referrable);
}
}
return indexed;
}
public Map<String, JvmIdentifiableElement> collectMandatoryFeatures(Component component) {
Map<String, JvmIdentifiableElement> result = Maps.newHashMap();
IScope scope = scopeProvider.createComponentFeaturesScope(component);
for(IEObjectDescription description: scope.getAllElements()) {
JvmIdentifiableElement jvmFeature = (JvmIdentifiableElement) description.getEObjectOrProxy();
if (isMandatory(jvmFeature)) {
result.put(qualifiedNameConverter.toString(description.getName()), jvmFeature);
}
}
return result;
}
public boolean isMandatory(JvmIdentifiableElement feature) {
if (feature.eIsProxy())
return false;
if (feature instanceof DeclaredProperty) {
return ((DeclaredProperty) feature).getDefault() == null;
}
JvmOperation operation = (JvmOperation) feature;
for(JvmAnnotationReference annotation: operation.getAnnotations()) {
if (Mandatory.class.getName().equals(annotation.getAnnotation().getIdentifier()))
return true;
}
return false;
}
@Override
protected List<EPackage> getEPackages() {
List<EPackage> ePackages = super.getEPackages();
ePackages.add(Mwe2Package.eINSTANCE);
return ePackages;
}
}