/******************************************************************************* * 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; } }