org.eclipse.xtend.core.validation.XtendValidator.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.xtend.core.validation.XtendValidator.java

Source

/*******************************************************************************
 * Copyright (c) 2011, 2018 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.xtend.core.validation;

import static com.google.common.collect.Iterables.*;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Lists.*;
import static com.google.common.collect.Lists.transform;
import static com.google.common.collect.Sets.*;
import static org.eclipse.xtend.core.validation.IssueCodes.*;
import static org.eclipse.xtend.core.xtend.XtendPackage.Literals.*;
import static org.eclipse.xtext.util.JavaVersion.*;
import static org.eclipse.xtext.util.Strings.*;
import static org.eclipse.xtext.util.Strings.isEmpty;
import static org.eclipse.xtext.xbase.XbasePackage.Literals.*;
import static org.eclipse.xtext.xbase.validation.IssueCodes.*;

import java.lang.annotation.ElementType;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtend.core.jvmmodel.DispatchHelper;
import org.eclipse.xtend.core.jvmmodel.IXtendJvmAssociations;
import org.eclipse.xtend.core.richstring.RichStringProcessor;
import org.eclipse.xtend.core.typesystem.LocalClassAwareTypeNames;
import org.eclipse.xtend.core.xtend.AnonymousClass;
import org.eclipse.xtend.core.xtend.RichString;
import org.eclipse.xtend.core.xtend.RichStringElseIf;
import org.eclipse.xtend.core.xtend.RichStringForLoop;
import org.eclipse.xtend.core.xtend.RichStringIf;
import org.eclipse.xtend.core.xtend.XtendAnnotationTarget;
import org.eclipse.xtend.core.xtend.XtendAnnotationType;
import org.eclipse.xtend.core.xtend.XtendClass;
import org.eclipse.xtend.core.xtend.XtendConstructor;
import org.eclipse.xtend.core.xtend.XtendEnum;
import org.eclipse.xtend.core.xtend.XtendExecutable;
import org.eclipse.xtend.core.xtend.XtendField;
import org.eclipse.xtend.core.xtend.XtendFile;
import org.eclipse.xtend.core.xtend.XtendFormalParameter;
import org.eclipse.xtend.core.xtend.XtendFunction;
import org.eclipse.xtend.core.xtend.XtendInterface;
import org.eclipse.xtend.core.xtend.XtendMember;
import org.eclipse.xtend.core.xtend.XtendPackage;
import org.eclipse.xtend.core.xtend.XtendParameter;
import org.eclipse.xtend.core.xtend.XtendTypeDeclaration;
import org.eclipse.xtend.core.xtend.XtendVariableDeclaration;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.common.types.JvmAnnotationTarget;
import org.eclipse.xtext.common.types.JvmAnnotationType;
import org.eclipse.xtext.common.types.JvmConstructor;
import org.eclipse.xtext.common.types.JvmDeclaredType;
import org.eclipse.xtext.common.types.JvmExecutable;
import org.eclipse.xtext.common.types.JvmFeature;
import org.eclipse.xtext.common.types.JvmField;
import org.eclipse.xtext.common.types.JvmFormalParameter;
import org.eclipse.xtext.common.types.JvmGenericType;
import org.eclipse.xtext.common.types.JvmIdentifiableElement;
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.JvmSpecializedTypeReference;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.JvmTypeParameter;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.common.types.JvmVisibility;
import org.eclipse.xtext.common.types.JvmWildcardTypeReference;
import org.eclipse.xtext.common.types.TypesPackage;
import org.eclipse.xtext.common.types.util.AnnotationLookup;
import org.eclipse.xtext.common.types.util.DeprecationUtil;
import org.eclipse.xtext.common.types.util.TypeReferences;
import org.eclipse.xtext.diagnostics.Severity;
import org.eclipse.xtext.documentation.IEObjectDocumentationProvider;
import org.eclipse.xtext.documentation.IEObjectDocumentationProviderExtension;
import org.eclipse.xtext.documentation.IJavaDocTypeReferenceProvider;
import org.eclipse.xtext.naming.IQualifiedNameConverter;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.scoping.IScope;
import org.eclipse.xtext.scoping.IScopeProvider;
import org.eclipse.xtext.util.JavaVersion;
import org.eclipse.xtext.util.ReplaceRegion;
import org.eclipse.xtext.validation.Check;
import org.eclipse.xtext.validation.ComposedChecks;
import org.eclipse.xtext.validation.ValidationMessageAcceptor;
import org.eclipse.xtext.xbase.XAssignment;
import org.eclipse.xtext.xbase.XBlockExpression;
import org.eclipse.xtext.xbase.XCatchClause;
import org.eclipse.xtext.xbase.XClosure;
import org.eclipse.xtext.xbase.XExpression;
import org.eclipse.xtext.xbase.XFeatureCall;
import org.eclipse.xtext.xbase.XReturnExpression;
import org.eclipse.xtext.xbase.XbasePackage;
import org.eclipse.xtext.xbase.annotations.typing.XAnnotationUtil;
import org.eclipse.xtext.xbase.annotations.validation.XbaseWithAnnotationsValidator;
import org.eclipse.xtext.xbase.annotations.xAnnotations.XAnnotation;
import org.eclipse.xtext.xbase.annotations.xAnnotations.XAnnotationsPackage;
import org.eclipse.xtext.xbase.compiler.GeneratorConfig;
import org.eclipse.xtext.xbase.compiler.IGeneratorConfigProvider;
import org.eclipse.xtext.xbase.compiler.JavaKeywords;
import org.eclipse.xtext.xbase.jvmmodel.IJvmModelAssociations;
import org.eclipse.xtext.xbase.jvmmodel.ILogicalContainerProvider;
import org.eclipse.xtext.xbase.jvmmodel.JvmTypeExtensions;
import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
import org.eclipse.xtext.xbase.scoping.batch.IFeatureNames;
import org.eclipse.xtext.xbase.scoping.featurecalls.OperatorMapping;
import org.eclipse.xtext.xbase.typesystem.IBatchTypeResolver;
import org.eclipse.xtext.xbase.typesystem.IResolvedTypes;
import org.eclipse.xtext.xbase.typesystem.override.ConflictingDefaultOperation;
import org.eclipse.xtext.xbase.typesystem.override.IOverrideCheckResult.OverrideCheckDetails;
import org.eclipse.xtext.xbase.typesystem.override.IResolvedConstructor;
import org.eclipse.xtext.xbase.typesystem.override.IResolvedExecutable;
import org.eclipse.xtext.xbase.typesystem.override.IResolvedOperation;
import org.eclipse.xtext.xbase.typesystem.override.OverrideHelper;
import org.eclipse.xtext.xbase.typesystem.override.ResolvedFeatures;
import org.eclipse.xtext.xbase.typesystem.references.ITypeReferenceOwner;
import org.eclipse.xtext.xbase.typesystem.references.LightweightTypeReference;
import org.eclipse.xtext.xbase.typesystem.references.StandardTypeReferenceOwner;
import org.eclipse.xtext.xbase.typesystem.util.ContextualVisibilityHelper;
import org.eclipse.xtext.xbase.typesystem.util.IVisibilityHelper;
import org.eclipse.xtext.xbase.typesystem.util.RecursionGuard;
import org.eclipse.xtext.xbase.validation.ImplicitReturnFinder;
import org.eclipse.xtext.xbase.validation.ImplicitReturnFinder.Acceptor;
import org.eclipse.xtext.xbase.validation.ProxyAwareUIStrings;
import org.eclipse.xtext.xbase.validation.UIStrings;
import org.eclipse.xtext.xtype.XComputedTypeReference;
import org.eclipse.xtext.xtype.XImportDeclaration;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.google.inject.Inject;

/**
 * @author Jan Koehnlein - Initial contribution and API
 * @author Sebastian Zarnekow
 * @author Sven Efftinge
 * @author Holger Schill
 * @author Stephane Galland
 */
@ComposedChecks(validators = { AnnotationValidation.class })
public class XtendValidator extends XbaseWithAnnotationsValidator {

    @Inject
    private RichStringProcessor richStringProcessor;

    @Inject
    private IXtendJvmAssociations associations;

    @Inject
    private OverrideHelper overrideHelper;

    @Inject
    private DispatchHelper dispatchHelper;

    @Inject
    private XAnnotationUtil annotationUtil;

    @Inject
    private JavaKeywords javaUtils;

    @Inject
    private UIStrings uiStrings;

    @Inject
    private ILogicalContainerProvider containerProvider;

    @Inject
    private JvmTypeExtensions typeExtensions;

    @Inject
    private IJvmModelAssociations jvmModelAssociations;

    @Inject
    private IVisibilityHelper visibilityHelper;

    @Inject
    private IJavaDocTypeReferenceProvider javaDocTypeReferenceProvider;

    @Inject
    private IScopeProvider scopeProvider;

    @Inject
    private IEObjectDocumentationProvider documentationProvider;

    @Inject
    private IQualifiedNameConverter qualifiedNameConverter;

    @Inject
    private OperatorMapping operatorMapping;

    @Inject
    private ImplicitReturnFinder implicitReturnFinder;

    @Inject
    private LocalClassAwareTypeNames localClassAwareTypeNames;

    @Inject
    private IBatchTypeResolver batchTypeResolver;

    @Inject
    private ProxyAwareUIStrings proxyAwareUIStrings;

    @Inject
    private AnnotationLookup annotationLookup;

    @Inject
    private IGeneratorConfigProvider generatorConfigProvider;

    protected final Set<String> visibilityModifers = ImmutableSet.of("public", "private", "protected", "package");

    protected final Set<String> junitAnnotations = ImmutableSet.of(
            // JUnit4 annotations
            "org.junit.Test", "org.junit.Before", "org.junit.After", "org.junit.BeforeClass",
            "org.junit.AfterClass",

            // JUnit5 annotations
            "org.junit.jupiter.api.Test", "org.junit.jupiter.api.BeforeEach", "org.junit.jupiter.api.AfterEach",
            "org.junit.jupiter.api.BeforeAll", "org.junit.jupiter.api.AfterAll");

    protected final Multimap<Class<?>, ElementType> targetInfos;

    {
        ImmutableMultimap.Builder<Class<?>, ElementType> result = ImmutableMultimap.builder();
        result.put(XtendClass.class, ElementType.TYPE);
        result.put(XtendInterface.class, ElementType.TYPE);
        result.put(XtendEnum.class, ElementType.TYPE);
        result.putAll(XtendAnnotationType.class, ElementType.ANNOTATION_TYPE, ElementType.TYPE);
        result.put(XtendField.class, ElementType.FIELD);
        result.put(XtendFunction.class, ElementType.METHOD);
        result.put(XtendParameter.class, ElementType.PARAMETER);
        targetInfos = result.build();
    }

    @Override
    protected List<EPackage> getEPackages() {
        List<EPackage> ePackages = newArrayList(super.getEPackages());
        ePackages.add(XtendPackage.eINSTANCE);
        return ePackages;
    }

    protected GeneratorConfig getGeneratorConfig(EObject element) {
        GeneratorConfig result = (GeneratorConfig) getContext().get(GeneratorConfig.class);
        if (result == null) {
            result = generatorConfigProvider.get(element);
            getContext().put(GeneratorConfig.class, result);
            if (result.getJavaSourceVersion().isAtLeast(JAVA8)) {
                methodInInterfaceModifierValidator = new ModifierValidator(
                        newArrayList("public", "abstract", "static", "def", "override"), this);
            } else {
                methodInInterfaceModifierValidator = new ModifierValidator(
                        newArrayList("public", "abstract", "def", "override"), this);
            }
        }
        return result;
    }

    protected boolean hasAnnotation(XtendAnnotationTarget source, Class<?> class1) {
        for (XAnnotation anno : source.getAnnotations()) {
            if (anno != null && anno.getAnnotationType() != null
                    && class1.getName().equals(anno.getAnnotationType().getIdentifier()))
                return true;
        }
        return false;
    }

    @Check
    public void checkValidExtension(XtendField field) {
        if (field.isExtension()) {
            JvmField jvmField = associations.getJvmField(field);
            if (jvmField != null) {
                checkValidExtensionType(jvmField, field, XtendPackage.Literals.XTEND_FIELD__TYPE);
            }
        }
    }

    @Check
    public void checkNonRawTypeInferred(XtendField field) {
        if (field.getType() == null) {
            JvmField jvmField = associations.getJvmField(field);
            // field could have been removed by AA, thus the resource is possibly null
            if (jvmField != null && jvmField.eResource() != null) {
                JvmTypeReference fieldType = jvmField.getType();
                validateInferredType(fieldType, field, "The inferred field type ", XTEND_FIELD__NAME);
            }
        }
    }

    @Check
    public void checkNonRawTypeInferred(XtendFunction function) {
        if (function.getReturnType() == null) {
            JvmOperation operation = associations.getDirectlyInferredOperation(function);
            // operation could have been removed by AA, thus the resource is possibly null
            if (operation != null && operation.eResource() != null) {
                JvmTypeReference returnType = operation.getReturnType();
                validateInferredType(returnType, function, "The inferred return type ", XTEND_FUNCTION__NAME);
            }
        }
    }

    @Check
    public void checkJUnitMethodReturnType(XtendFunction function) {
        JvmOperation operation = associations.getDirectlyInferredOperation(function);

        /*
         * Active annotations could also change the return type.
         * Checking that the JvmOperation really has a JUnit annotation.
         */
        if (hasJUnitAnnotation(operation)) {
            LightweightTypeReference actualType = determineReturnType(operation);
            if (actualType != null && !actualType.isUnknown() && !actualType.isPrimitiveVoid()) {
                String message = String.format("JUnit method %s() must be void but is %s.", function.getName(),
                        actualType.getHumanReadableName());
                EAttribute location = XTEND_FUNCTION__NAME;
                error(message, function, location, INVALID_RETURN_TYPE_IN_CASE_OF_JUNIT_ANNOTATION);
            }
        }
    }

    private LightweightTypeReference determineReturnType(JvmOperation operation) {
        // operation could have been removed by AA, thus the resource is possibly null
        if (operation != null && operation.eResource() != null) {
            return batchTypeResolver.resolveTypes(operation).getActualType(operation);
        }
        return null;
    }

    private boolean hasJUnitAnnotation(JvmOperation operation) {
        return !annotationUtil.findAnnotations(junitAnnotations, operation).isEmpty();
    }

    protected void validateInferredType(JvmTypeReference inferredType, XtendMember member, String messagePrefix,
            EAttribute location) {
        if (inferredType != null) {
            TreeIterator<EObject> iterator = EcoreUtil2.eAll(inferredType);
            while (iterator.hasNext()) {
                EObject next = iterator.next();
                if (next instanceof JvmParameterizedTypeReference) {
                    JvmParameterizedTypeReference candidate = (JvmParameterizedTypeReference) next;
                    JvmType type = candidate.getType();
                    if (type instanceof JvmGenericType && !((JvmGenericType) type).getTypeParameters().isEmpty()) {
                        if (candidate.getArguments().isEmpty()) {
                            StringBuilder message = new StringBuilder(messagePrefix);
                            message = proxyAwareUIStrings.visit(inferredType, message);
                            if (message != null) {
                                message.append(" uses the raw type ");
                                message.append(type.getSimpleName());
                                message.append(". References to generic type ");
                                message = proxyAwareUIStrings.appendTypeSignature(type, message);
                                message.append(" should be parameterized");
                                warning(message.toString(), member, location,
                                        org.eclipse.xtext.xbase.validation.IssueCodes.RAW_TYPE);
                            }
                            return;
                        }
                    }
                } else if (next instanceof XComputedTypeReference) {
                    validateInferredType(((XComputedTypeReference) next).getEquivalent(), member, messagePrefix,
                            location);
                    iterator.prune();
                }
            }
        }
    }

    @Check
    public void checkValidExtension(XtendFormalParameter parameter) {
        // catch clauses validate their types against java.lang.Throwable
        if (parameter.isExtension() && !(parameter.eContainer() instanceof XCatchClause))
            checkValidExtensionType(parameter, parameter,
                    TypesPackage.Literals.JVM_FORMAL_PARAMETER__PARAMETER_TYPE);
    }

    @Check
    public void checkValidExtension(XtendVariableDeclaration variableDeclaration) {
        if (variableDeclaration.isExtension())
            checkValidExtensionType(variableDeclaration, variableDeclaration,
                    XbasePackage.Literals.XVARIABLE_DECLARATION__NAME);
    }

    @Check
    public void checkValidExtension(XtendParameter parameter) {
        if (parameter.isExtension()) {
            JvmFormalParameter jvmParameter = associations.getJvmParameter(parameter);
            if (jvmParameter != null)
                checkValidExtensionType(jvmParameter, parameter,
                        XtendPackage.Literals.XTEND_PARAMETER__PARAMETER_TYPE);
        }
    }

    protected void checkValidExtensionType(JvmIdentifiableElement identifiable, EObject source,
            EStructuralFeature feature) {
        LightweightTypeReference type = getActualType(identifiable);
        if (type != null && type.isPrimitive()) {
            error(String.format("The primitive type %s is not a valid extension", type.getHumanReadableName()),
                    source, feature, INVALID_EXTENSION_TYPE);
        }
    }

    @Check
    public void checkAnnotationTarget(XAnnotation annotation) {
        JvmType annotationType = annotation.getAnnotationType();
        if (annotationType == null || annotationType.eIsProxy() || !(annotationType instanceof JvmAnnotationType)) {
            return;
        }
        Set<ElementType> targets = annotationUtil.getAnnotationTargets((JvmAnnotationType) annotationType);
        if (targets.isEmpty())
            return;
        final EObject eContainer = getContainingAnnotationTarget(annotation);
        Class<? extends EObject> clazz = eContainer.getClass();
        if (eContainer instanceof XtendField && eContainer.eContainer() instanceof XtendAnnotationType) {
            clazz = XtendFunction.class;
        }
        for (Entry<Class<?>, Collection<ElementType>> mapping : targetInfos.asMap().entrySet()) {
            if (mapping.getKey().isAssignableFrom(clazz)) {
                targets.retainAll(mapping.getValue());
                if (targets.isEmpty()) {
                    error("The annotation @" + annotation.getAnnotationType().getSimpleName()
                            + " is disallowed for this location.", annotation, null, INSIGNIFICANT_INDEX,
                            ANNOTATION_WRONG_TARGET);
                }
            }
        }
    }

    @Check
    public void checkMultipleAnnotations(final XtendAnnotationTarget annotationTarget) {
        if (annotationTarget.getAnnotations().size() <= 1 || !isRelevantAnnotationTarget(annotationTarget)) {
            return;
        }

        ImmutableListMultimap<String, XAnnotation> groupByIdentifier = Multimaps
                .index(annotationTarget.getAnnotations(), new Function<XAnnotation, String>() {
                    @Override
                    public String apply(XAnnotation input) {
                        return input.getAnnotationType().getIdentifier();
                    }
                });

        for (String qName : groupByIdentifier.keySet()) {
            ImmutableList<XAnnotation> sameType = groupByIdentifier.get(qName);
            if (sameType.size() > 1) {
                JvmType type = sameType.get(0).getAnnotationType();
                if (type instanceof JvmAnnotationType && !type.eIsProxy()
                        && !annotationLookup.isRepeatable((JvmAnnotationType) type)) {
                    for (XAnnotation xAnnotation : sameType) {
                        error("Multiple annotations of non-repeatable type @"
                                + xAnnotation.getAnnotationType().getSimpleName()
                                + ". Only annotation types marked @Repeatable can be used multiple times at one target.",
                                xAnnotation, XAnnotationsPackage.Literals.XANNOTATION__ANNOTATION_TYPE,
                                INSIGNIFICANT_INDEX, ANNOTATION_MULTIPLE);
                    }
                }
            }
        }
    }

    protected boolean isRelevantAnnotationTarget(final XtendAnnotationTarget annotationTarget) {
        return any(targetInfos.keySet(), new Predicate<Class<?>>() {
            @Override
            public boolean apply(Class<?> input) {
                return input.isInstance(annotationTarget);
            }
        });
    }

    protected EObject getContainingAnnotationTarget(XAnnotation annotation) {
        final EObject eContainer = annotation.eContainer();
        // skip synthetic container
        if (eContainer.eClass() == XtendPackage.Literals.XTEND_MEMBER
                || eContainer.eClass() == XtendPackage.Literals.XTEND_TYPE_DECLARATION) {
            return eContainer.eContainer();
        }
        return eContainer;
    }

    @Check
    public void checkNoVoidInDependencyDeclaration(XtendField dep) {
        JvmTypeReference declaredFieldType = dep.getType();
        if (isPrimitiveVoid(declaredFieldType)) {
            error("Primitive void cannot be a dependency.", dep.getType(), null, INVALID_USE_OF_TYPE);
        }
    }

    @Check
    public void checkNoTypeNameShadowing(XtendTypeDeclaration type) {
        String name = type.getName();
        if (name != null && name.length() > 0) {
            XtendTypeDeclaration outer = EcoreUtil2.getContainerOfType(type.eContainer(),
                    XtendTypeDeclaration.class);
            while (outer != null) {
                if (name.equals(outer.getName())) {
                    acceptError("The nested type " + name + " cannot hide an enclosing type", type,
                            XtendPackage.Literals.XTEND_TYPE_DECLARATION__NAME, INSIGNIFICANT_INDEX,
                            INVALID_MEMBER_NAME);
                    return;
                }
                outer = EcoreUtil2.getContainerOfType(outer.eContainer(), XtendTypeDeclaration.class);
            }
        }
    }

    @Check
    public void checkMemberNamesAreUnique(XtendTypeDeclaration xtendType) {
        final Multimap<String, XtendField> name2field = HashMultimap.create();
        final Multimap<String, XtendTypeDeclaration> name2type = HashMultimap.create();
        final Multimap<JvmType, XtendField> type2extension = HashMultimap.create();
        for (XtendMember member : xtendType.getMembers()) {
            if (member instanceof XtendField) {
                XtendField field = (XtendField) member;
                if (isEmpty(field.getName())) {
                    if (field.isExtension()) {
                        JvmTypeReference typeReference = field.getType();
                        if (typeReference != null) {
                            JvmType type = typeReference.getType();
                            if (type != null)
                                type2extension.put(type, field);
                        }
                    }
                } else {
                    name2field.put(field.getName(), field);
                }
            } else if (member instanceof XtendTypeDeclaration) {
                String name = ((XtendTypeDeclaration) member).getName();
                if (name != null && name.length() > 0) {
                    name2type.put(name, (XtendTypeDeclaration) member);
                }
            }
        }
        for (String name : name2field.keySet()) {
            Collection<XtendField> fields = name2field.get(name);
            if (fields.size() > 1) {
                for (XtendField field : fields)
                    error("Duplicate field " + name, field, XtendPackage.Literals.XTEND_FIELD__NAME,
                            DUPLICATE_FIELD);
            }
        }
        for (String name : name2type.keySet()) {
            Collection<XtendTypeDeclaration> types = name2type.get(name);
            if (types.size() > 1) {
                for (XtendTypeDeclaration type : types)
                    error("Duplicate nested type " + name, type, XtendPackage.Literals.XTEND_TYPE_DECLARATION__NAME,
                            DUPLICATE_TYPE_NAME);
            }
        }
        for (JvmType type : type2extension.keySet()) {
            Collection<XtendField> fields = type2extension.get(type);
            if (fields.size() > 1) {
                for (XtendField field : fields)
                    error("Duplicate extension with same type", field, XTEND_FIELD__TYPE, DUPLICATE_FIELD);
            }
        }
    }

    @Check
    public void checkXtendParameterNotPrimitiveVoid(XtendParameter param) {
        if (isPrimitiveVoid(param.getParameterType())) {
            XtendFunction function = (XtendFunction) (param.eContainer() instanceof XtendFunction
                    ? param.eContainer()
                    : null);
            if (function != null)
                error("void is an invalid type for the parameter " + param.getName() + " of the method "
                        + function.getName(), param.getParameterType(), null, INVALID_USE_OF_TYPE);
            else
                error("void is an invalid type for the parameter " + param.getName(), param.getParameterType(),
                        null, INVALID_USE_OF_TYPE);
        }
    }

    @Check
    public void checkVarArgIsNotExtension(XtendParameter param) {
        if (param.isVarArg() && param.isExtension()) {
            error("A vararg may not be an extension.", param, XTEND_PARAMETER__EXTENSION, INVALID_USE_OF_VAR_ARG);
        }
    }

    @Check
    public void checkVarArgComesLast(XtendParameter param) {
        if (param.isVarArg()) {
            @SuppressWarnings("unchecked")
            List<XtendParameter> params = (List<XtendParameter>) param.eContainer()
                    .eGet(param.eContainingFeature());
            if (param != Iterables.getLast(params)) {
                error("A vararg must be the last parameter.", param, XTEND_PARAMETER__VAR_ARG,
                        INVALID_USE_OF_VAR_ARG);
            }
        }
    }

    @Check
    public void checkClassPath(XtendFile xtendFile) {
        TypeReferences typeReferences = getServices().getTypeReferences();
        final JvmGenericType listType = (JvmGenericType) typeReferences.findDeclaredType(List.class, xtendFile);
        if (listType == null || listType.getTypeParameters().isEmpty()) {
            error("Couldn't find a JDK 1.5 or higher on the project's classpath.", xtendFile, XTEND_FILE__PACKAGE,
                    IssueCodes.JDK_NOT_ON_CLASSPATH);
        } else if (typeReferences.findDeclaredType(ToStringBuilder.class, xtendFile) == null) {
            error("Couldn't find the mandatory library 'org.eclipse.xtext.xbase.lib' 2.8.0 or higher on the project's classpath.",
                    xtendFile, XTEND_FILE__PACKAGE, IssueCodes.XBASE_LIB_NOT_ON_CLASSPATH);
        }
    }

    @Check
    public void checkWhitespaceInRichStrings(RichString richString) {
        // don't check the indentation of nested rich strings in 
        // IF and FOR individually
        if (richString.eContainer() instanceof RichStringIf) {
            RichStringIf container = (RichStringIf) richString.eContainer();
            if (container.getThen() == richString || container.getElse() == richString)
                return;
        }
        if (richString.eContainer() instanceof RichStringElseIf) {
            RichStringElseIf container = (RichStringElseIf) richString.eContainer();
            if (container.getThen() == richString)
                return;
        }
        if (richString.eContainer() instanceof RichStringForLoop) {
            RichStringForLoop container = (RichStringForLoop) richString.eContainer();
            if (container.getEachExpression() == richString)
                return;
        }
        doCheckWhitespaceIn(richString);
    }

    protected void doCheckWhitespaceIn(RichString richString) {
        ValidatingRichStringAcceptor helper = new ValidatingRichStringAcceptor(this);
        richStringProcessor.process(richString, helper, helper);
    }

    @Check
    public void checkSuperTypes(XtendClass xtendClass) {
        JvmTypeReference superClass = xtendClass.getExtends();
        if (superClass != null && superClass.getType() != null) {
            if (!(superClass.getType() instanceof JvmGenericType)
                    || ((JvmGenericType) superClass.getType()).isInterface()) {
                error("Superclass must be a class", XTEND_CLASS__EXTENDS, CLASS_EXPECTED);
            } else {
                if (((JvmGenericType) superClass.getType()).isFinal()) {
                    error("Attempt to override final class", XTEND_CLASS__EXTENDS, OVERRIDDEN_FINAL);
                }
                checkWildcardSupertype(xtendClass, superClass, XTEND_CLASS__EXTENDS, INSIGNIFICANT_INDEX);
            }
        }
        for (int i = 0; i < xtendClass.getImplements().size(); ++i) {
            JvmTypeReference implementedType = xtendClass.getImplements().get(i);
            if (!isInterface(implementedType.getType()) && !isAnnotation(implementedType.getType())) {
                error("Implemented interface must be an interface", XTEND_CLASS__IMPLEMENTS, i, INTERFACE_EXPECTED);
            }
            checkWildcardSupertype(xtendClass, implementedType, XTEND_CLASS__IMPLEMENTS, i);
        }
        JvmGenericType inferredType = associations.getInferredType(xtendClass);
        if (inferredType != null && hasCycleInHierarchy(inferredType, Sets.<JvmGenericType>newHashSet())) {
            error("The inheritance hierarchy of " + notNull(xtendClass.getName()) + " contains cycles",
                    XTEND_TYPE_DECLARATION__NAME, CYCLIC_INHERITANCE);
        }
    }

    @Check
    public void checkSuperTypes(XtendInterface xtendInterface) {
        for (int i = 0; i < xtendInterface.getExtends().size(); ++i) {
            JvmTypeReference extendedType = xtendInterface.getExtends().get(i);
            if (!isInterface(extendedType.getType()) && !isAnnotation(extendedType.getType())) {
                error("Extended interface must be an interface", XTEND_INTERFACE__EXTENDS, i, INTERFACE_EXPECTED);
            }
            checkWildcardSupertype(xtendInterface, extendedType, XTEND_INTERFACE__EXTENDS, i);
        }
        JvmGenericType inferredType = associations.getInferredType(xtendInterface);
        if (inferredType != null && hasCycleInHierarchy(inferredType, Sets.<JvmGenericType>newHashSet())) {
            error("The inheritance hierarchy of " + notNull(xtendInterface.getName()) + " contains cycles",
                    XTEND_TYPE_DECLARATION__NAME, CYCLIC_INHERITANCE);
        }
    }

    protected boolean isAnnotation(JvmType jvmType) {
        return jvmType instanceof JvmAnnotationType;
    }

    @Check
    public void checkSuperTypes(AnonymousClass anonymousClass) {
        JvmGenericType inferredType = associations.getInferredType(anonymousClass);
        if (inferredType != null) {
            JvmTypeReference superTypeRef = Iterables.getLast(inferredType.getSuperTypes());
            JvmType superType = superTypeRef.getType();
            if (superType instanceof JvmGenericType && ((JvmGenericType) superType).isFinal())
                error("Attempt to override final class", anonymousClass.getConstructorCall(),
                        XCONSTRUCTOR_CALL__CONSTRUCTOR, INSIGNIFICANT_INDEX, OVERRIDDEN_FINAL);
        }
    }

    @Check
    public void checkStaticMembers(AnonymousClass anonymousClass) {
        for (XtendMember member : anonymousClass.getMembers()) {
            if (member.isStatic()) {
                if (member instanceof XtendExecutable) {
                    error("A method of an anonymous class cannot be static.", member, XTEND_MEMBER__MODIFIERS,
                            INSIGNIFICANT_INDEX, ANONYMOUS_CLASS_STATIC_METHOD);
                } else if (member instanceof XtendField) {
                    JvmField field = (JvmField) jvmModelAssociations.getPrimaryJvmElement(member);
                    if (!member.isFinal()) {
                        error("A static field of an anonymous class must be final.", member,
                                XTEND_MEMBER__MODIFIERS, INSIGNIFICANT_INDEX, ANONYMOUS_CLASS_STATIC_FIELD);
                    } else if (!field.isConstant()) {
                        error("A static field of an anonymous class must be initialized with a constant expression.",
                                member, XTEND_FIELD__INITIAL_VALUE, INSIGNIFICANT_INDEX,
                                ANONYMOUS_CLASS_STATIC_FIELD);
                    }
                }
            }
        }
    }

    protected void checkWildcardSupertype(XtendTypeDeclaration xtendType, JvmTypeReference superTypeReference,
            EStructuralFeature feature, int index) {
        if (isInvalidWildcard(superTypeReference))
            error("The type " + notNull(xtendType.getName()) + " cannot extend or implement "
                    + superTypeReference.getIdentifier() + ". A supertype may not specify any wildcard", feature,
                    index, WILDCARD_IN_SUPERTYPE);
    }

    protected boolean isInvalidWildcard(JvmTypeReference typeRef) {
        if (typeRef instanceof JvmWildcardTypeReference)
            return true;
        else if (typeRef instanceof JvmParameterizedTypeReference) {
            for (JvmTypeReference typeArgument : ((JvmParameterizedTypeReference) typeRef).getArguments()) {
                if (typeArgument instanceof JvmWildcardTypeReference)
                    return true;
            }
        } else if (typeRef instanceof JvmSpecializedTypeReference) {
            return isInvalidWildcard(((JvmSpecializedTypeReference) typeRef).getEquivalent());
        }
        return false;
    }

    protected boolean hasCycleInHierarchy(JvmGenericType type, Set<JvmGenericType> processedSuperTypes) {
        JvmDeclaredType container = type;
        do {
            if (processedSuperTypes.contains(container))
                return true;
            container = container.getDeclaringType();
        } while (container != null);
        processedSuperTypes.add(type);
        for (JvmTypeReference superTypeRef : type.getSuperTypes()) {
            if (superTypeRef.getType() instanceof JvmGenericType) {
                if (hasCycleInHierarchy((JvmGenericType) superTypeRef.getType(), processedSuperTypes))
                    return true;
            }
        }
        processedSuperTypes.remove(type);
        return false;
    }

    @Check
    public void checkDuplicateAndOverriddenFunctions(XtendTypeDeclaration xtendType) {
        final JvmDeclaredType inferredType = associations.getInferredType(xtendType);
        if (inferredType instanceof JvmGenericType) {
            JavaVersion targetVersion = getGeneratorConfig(xtendType).getJavaSourceVersion();
            ResolvedFeatures resolvedFeatures = overrideHelper.getResolvedFeatures(inferredType, targetVersion);

            Set<EObject> flaggedOperations = Sets.newHashSet();
            doCheckDuplicateExecutables((JvmGenericType) inferredType, resolvedFeatures, flaggedOperations);
            doCheckOverriddenMethods(xtendType, (JvmGenericType) inferredType, resolvedFeatures, flaggedOperations);
            doCheckFunctionOverrides(resolvedFeatures, flaggedOperations);
        }
    }

    protected void doCheckDuplicateExecutables(JvmGenericType inferredType, final ResolvedFeatures resolvedFeatures,
            Set<EObject> flaggedOperations) {
        List<IResolvedOperation> declaredOperations = resolvedFeatures.getDeclaredOperations();
        doCheckDuplicateExecutables(inferredType, declaredOperations,
                new Function<String, List<IResolvedOperation>>() {
                    @Override
                    public List<IResolvedOperation> apply(String erasedSignature) {
                        return resolvedFeatures.getDeclaredOperations(erasedSignature);
                    }
                }, flaggedOperations);
        final List<IResolvedConstructor> declaredConstructors = resolvedFeatures.getDeclaredConstructors();
        doCheckDuplicateExecutables(inferredType, declaredConstructors,
                new Function<String, List<IResolvedConstructor>>() {
                    @Override
                    public List<IResolvedConstructor> apply(String erasedSignature) {
                        if (declaredConstructors.size() == 1) {
                            if (erasedSignature.equals(declaredConstructors.get(0).getResolvedErasureSignature())) {
                                return declaredConstructors;
                            }
                            return Collections.emptyList();
                        }
                        List<IResolvedConstructor> result = Lists
                                .newArrayListWithCapacity(declaredConstructors.size());
                        for (IResolvedConstructor constructor : declaredConstructors) {
                            if (erasedSignature.equals(constructor.getResolvedErasureSignature())) {
                                result.add(constructor);
                            }
                        }
                        return result;
                    }
                }, flaggedOperations);
    }

    protected <Executable extends IResolvedExecutable> void doCheckDuplicateExecutables(JvmGenericType inferredType,
            List<Executable> declaredOperations, Function<String, List<Executable>> bySignature,
            Set<EObject> flaggedOperations) {
        Set<Executable> processed = Sets.newHashSet();
        for (Executable declaredExecutable : declaredOperations) {
            if (!processed.contains(declaredExecutable)) {
                List<Executable> sameErasure = bySignature.apply(declaredExecutable.getResolvedErasureSignature());
                if (sameErasure.size() > 1) {
                    Multimap<String, Executable> perSignature = HashMultimap.create(sameErasure.size(), 2);
                    outer: for (Executable executable : sameErasure) {
                        for (LightweightTypeReference parameterType : executable.getResolvedParameterTypes()) {
                            if (parameterType.isUnknown())
                                continue outer;
                        }
                        perSignature.put(executable.getResolvedSignature(), executable);
                    }
                    if (perSignature.size() > 1) {
                        for (Collection<Executable> sameSignature : perSignature.asMap().values()) {
                            for (Executable operationWithSameSignature : sameSignature) {
                                JvmExecutable executable = operationWithSameSignature.getDeclaration();
                                EObject otherSource = associations.getPrimarySourceElement(executable);
                                if (flaggedOperations.add(otherSource)) {
                                    if (sameSignature.size() > 1) {
                                        error("Duplicate " + typeLabel(executable) + " "
                                                + operationWithSameSignature.getSimpleSignature() + " in type "
                                                + inferredType.getSimpleName(), otherSource,
                                                nameFeature(otherSource), DUPLICATE_METHOD);
                                    } else {
                                        error("The " + typeLabel(executable) + " "
                                                + operationWithSameSignature.getSimpleSignature()
                                                + " has the same erasure "
                                                + operationWithSameSignature.getResolvedErasureSignature()
                                                + " as another " + typeLabel(executable) + " in type "
                                                + inferredType.getSimpleName(), otherSource,
                                                nameFeature(otherSource), DUPLICATE_METHOD);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    protected String typeLabel(JvmExecutable executable) {
        if (executable instanceof JvmOperation)
            return "method";
        else if (executable instanceof JvmConstructor)
            return "constructor";
        else
            return "?";
    }

    protected EStructuralFeature nameFeature(EObject member) {
        if (member instanceof XtendFunction)
            return XTEND_FUNCTION__NAME;
        else if (member instanceof XtendConstructor)
            return null;
        else if (member instanceof XtendField)
            return XTEND_FIELD__NAME;
        else
            return null;
    }

    protected EStructuralFeature exceptionsFeature(EObject member) {
        if (member instanceof XtendExecutable)
            return XTEND_EXECUTABLE__EXCEPTIONS;
        else
            return null;
    }

    protected EStructuralFeature returnTypeFeature(EObject member) {
        if (member instanceof XtendFunction)
            return XTEND_FUNCTION__RETURN_TYPE;
        else
            return null;
    }

    protected void doCheckOverriddenMethods(XtendTypeDeclaration xtendType, JvmGenericType inferredType,
            ResolvedFeatures resolvedFeatures, Set<EObject> flaggedOperations) {
        List<IResolvedOperation> operationsMissingImplementation = null;
        boolean doCheckAbstract = !inferredType.isAbstract();
        if (doCheckAbstract) {
            operationsMissingImplementation = Lists.newArrayList();
        }
        IVisibilityHelper visibilityHelper = new ContextualVisibilityHelper(this.visibilityHelper,
                resolvedFeatures.getType());
        boolean flaggedType = false;
        for (IResolvedOperation operation : resolvedFeatures.getAllOperations()) {
            JvmDeclaredType operationDeclaringType = operation.getDeclaration().getDeclaringType();
            if (operationDeclaringType != inferredType) {
                if (operationsMissingImplementation != null && operation.getDeclaration().isAbstract()) {
                    operationsMissingImplementation.add(operation);
                }
                if (visibilityHelper.isVisible(operation.getDeclaration())) {
                    String erasureSignature = operation.getResolvedErasureSignature();
                    List<IResolvedOperation> declaredOperationsWithSameErasure = resolvedFeatures
                            .getDeclaredOperations(erasureSignature);
                    for (IResolvedOperation localOperation : declaredOperationsWithSameErasure) {
                        if (!localOperation.isOverridingOrImplementing(operation.getDeclaration())
                                .isOverridingOrImplementing()) {
                            EObject source = findPrimarySourceElement(localOperation);
                            if (flaggedOperations.add(source)) {
                                if (operation.getDeclaration().isStatic()
                                        && !localOperation.getDeclaration().isStatic()) {
                                    error("The instance method " + localOperation.getSimpleSignature()
                                            + " cannot override the static method " + operation.getSimpleSignature()
                                            + " of type " + getDeclaratorName(operation.getDeclaration()) + ".",
                                            source, nameFeature(source), DUPLICATE_METHOD);
                                } else {
                                    error("Name clash: The method " + localOperation.getSimpleSignature()
                                            + " of type " + inferredType.getSimpleName()
                                            + " has the same erasure as " +
                                            // use source with other operations parameters to avoid confusion
                                            // due to name transformations in JVM model inference
                                            operation.getSimpleSignature() + " of type "
                                            + getDeclaratorName(operation.getDeclaration())
                                            + " but does not override it.", source, nameFeature(source),
                                            DUPLICATE_METHOD);
                                }
                            }
                        }
                    }
                    if (operation instanceof ConflictingDefaultOperation
                            && contributesToConflict(inferredType, (ConflictingDefaultOperation) operation)
                            && !flaggedType) {
                        IResolvedOperation conflictingOperation = ((ConflictingDefaultOperation) operation)
                                .getConflictingOperations().get(0);
                        // Include the declaring class in the issue code in order to give better quick fixes
                        String[] uris = new String[] {
                                getDeclaratorName(operation.getDeclaration()) + "|"
                                        + EcoreUtil.getURI(operation.getDeclaration()).toString(),
                                getDeclaratorName(conflictingOperation.getDeclaration()) + "|"
                                        + EcoreUtil.getURI(conflictingOperation.getDeclaration()).toString() };
                        if (!operation.getDeclaration().isAbstract()
                                && !conflictingOperation.getDeclaration().isAbstract()) {
                            error("The type " + inferredType.getSimpleName()
                                    + " inherits multiple implementations of the method "
                                    + conflictingOperation.getSimpleSignature() + " from "
                                    + getDeclaratorName(conflictingOperation.getDeclaration()) + " and "
                                    + getDeclaratorName(operation.getDeclaration()) + ".", xtendType,
                                    XtendPackage.Literals.XTEND_TYPE_DECLARATION__NAME, CONFLICTING_DEFAULT_METHODS,
                                    uris);
                        } else {
                            // At least one of the operations is non-abstract
                            IResolvedOperation abstractOp, nonabstractOp;
                            if (operation.getDeclaration().isAbstract()) {
                                abstractOp = operation;
                                nonabstractOp = conflictingOperation;
                            } else {
                                abstractOp = conflictingOperation;
                                nonabstractOp = operation;
                            }
                            error("The non-abstract method " + nonabstractOp.getSimpleSignature()
                                    + " inherited from " + getDeclaratorName(nonabstractOp.getDeclaration())
                                    + " conflicts with the method " + abstractOp.getSimpleSignature()
                                    + " inherited from " + getDeclaratorName(abstractOp.getDeclaration()) + ".",
                                    xtendType, XtendPackage.Literals.XTEND_TYPE_DECLARATION__NAME,
                                    CONFLICTING_DEFAULT_METHODS, uris);
                        }
                        flaggedType = true;
                    }
                }
            }
        }
        if (operationsMissingImplementation != null && !operationsMissingImplementation.isEmpty() && !flaggedType) {
            reportMissingImplementations(xtendType, inferredType, operationsMissingImplementation);
        }
    }

    /**
     * Determine whether the given type contributes to the conflict caused by the given default interface implementation.
     */
    private boolean contributesToConflict(JvmGenericType rootType,
            ConflictingDefaultOperation conflictingDefaultOperation) {
        Set<JvmDeclaredType> involvedInterfaces = Sets.newHashSet();
        involvedInterfaces.add(conflictingDefaultOperation.getDeclaration().getDeclaringType());
        for (IResolvedOperation conflictingOperation : conflictingDefaultOperation.getConflictingOperations()) {
            involvedInterfaces.add(conflictingOperation.getDeclaration().getDeclaringType());
        }
        RecursionGuard<JvmDeclaredType> recursionGuard = new RecursionGuard<JvmDeclaredType>();
        if (rootType.isInterface()) {
            int contributingCount = 0;
            for (JvmTypeReference typeRef : rootType.getExtendedInterfaces()) {
                JvmType rawType = typeRef.getType();
                if (rawType instanceof JvmDeclaredType
                        && contributesToConflict((JvmDeclaredType) rawType, involvedInterfaces, recursionGuard)) {
                    contributingCount++;
                }
            }
            return contributingCount >= 2;
        } else {
            return contributesToConflict(rootType, involvedInterfaces, recursionGuard);
        }
    }

    private boolean contributesToConflict(JvmDeclaredType type, Set<JvmDeclaredType> involvedInterfaces,
            RecursionGuard<JvmDeclaredType> guard) {
        if (!guard.tryNext(type)) {
            return false;
        }
        if (involvedInterfaces.contains(type)) {
            return true;
        }
        for (JvmTypeReference typeRef : type.getExtendedInterfaces()) {
            JvmType rawType = typeRef.getType();
            if (rawType instanceof JvmDeclaredType
                    && contributesToConflict((JvmDeclaredType) rawType, involvedInterfaces, guard)) {
                return true;
            }
        }
        return false;
    }

    protected void reportMissingImplementations(XtendTypeDeclaration xtendClass, JvmGenericType inferredType,
            List<IResolvedOperation> operationsMissingImplementation) {
        StringBuilder errorMsg = new StringBuilder();
        String name = xtendClass.getName();
        boolean needsNewLine = operationsMissingImplementation.size() > 1;
        if (xtendClass.isAnonymous()) {
            JvmTypeReference superType = Iterables.getLast(inferredType.getSuperTypes());
            errorMsg.append("The anonymous subclass of ").append(superType.getSimpleName());
            errorMsg.append(" does not implement ");
        } else {
            errorMsg.append("The class ").append(name);
            errorMsg.append(" must be defined abstract because it does not implement ");
        }
        if (needsNewLine) {
            errorMsg.append("its inherited abstract methods ");
        }
        IResolvedOperation operation;
        for (int i = 0; i < operationsMissingImplementation.size() && i < 3; ++i) {
            operation = operationsMissingImplementation.get(i);
            if (needsNewLine)
                errorMsg.append("\n- ");
            errorMsg.append(operation.getSimpleSignature());
        }
        int numUnshownOperations = operationsMissingImplementation.size() - 3;
        if (numUnshownOperations > 0)
            errorMsg.append("\nand " + numUnshownOperations + " more.");
        List<String> uris = transform(operationsMissingImplementation, new Function<IResolvedOperation, String>() {
            @Override
            public String apply(IResolvedOperation from) {
                return EcoreUtil.getURI(from.getDeclaration()).toString();
            }
        });
        if (xtendClass.isAnonymous()) {
            error(errorMsg.toString(), xtendClass, ANONYMOUS_CLASS__CONSTRUCTOR_CALL,
                    ANONYMOUS_CLASS_MISSING_MEMBERS, toArray(uris, String.class));
        } else {
            error(errorMsg.toString(), xtendClass, XTEND_TYPE_DECLARATION__NAME, CLASS_MUST_BE_ABSTRACT,
                    toArray(uris, String.class));
        }
    }

    protected void doCheckFunctionOverrides(ResolvedFeatures resolvedFeatures, Set<EObject> flaggedOperations) {
        for (IResolvedOperation operation : resolvedFeatures.getDeclaredOperations()) {
            doCheckFunctionOverrides(operation, flaggedOperations);
        }
    }

    protected void doCheckFunctionOverrides(IResolvedOperation operation, Set<EObject> flaggedOperations) {
        EObject sourceElement = findPrimarySourceElement(operation);
        if (sourceElement != null) {
            List<IResolvedOperation> allInherited = operation.getOverriddenAndImplementedMethods();
            if (allInherited.isEmpty()) {
                if (sourceElement instanceof XtendFunction && flaggedOperations.add(sourceElement)) {
                    XtendFunction function = (XtendFunction) sourceElement;
                    if (function.isOverride()) {
                        error("The method " + operation.getSimpleSignature() + " of type "
                                + getDeclaratorName(operation.getDeclaration())
                                + " must override a superclass method.", function, XTEND_MEMBER__MODIFIERS,
                                function.getModifiers().indexOf("override"), OBSOLETE_OVERRIDE);
                    } else {
                        for (XAnnotation anno : function.getAnnotations()) {
                            if (anno != null && anno.getAnnotationType() != null
                                    && Override.class.getName().equals(anno.getAnnotationType().getIdentifier())) {
                                error("Superfluous @Override annotation", anno, null, OBSOLETE_ANNOTATION_OVERRIDE);
                            }
                        }
                    }
                }
            } else if (flaggedOperations.add(sourceElement)) {
                doCheckFunctionOverrides(sourceElement, operation, allInherited);
            }
        }
    }

    protected void doCheckFunctionOverrides(EObject sourceElement, IResolvedOperation resolved,
            List<IResolvedOperation> allInherited) {
        boolean overrideProblems = false;
        List<IResolvedOperation> exceptionMismatch = null;
        for (IResolvedOperation inherited : allInherited) {
            if (inherited.getOverrideCheckResult().hasProblems()) {
                overrideProblems = true;
                EnumSet<OverrideCheckDetails> details = inherited.getOverrideCheckResult().getDetails();
                if (details.contains(OverrideCheckDetails.IS_FINAL)) {
                    error("Attempt to override final method " + inherited.getSimpleSignature(), sourceElement,
                            nameFeature(sourceElement), OVERRIDDEN_FINAL);
                } else if (details.contains(OverrideCheckDetails.REDUCED_VISIBILITY)) {
                    error("Cannot reduce the visibility of the overridden method " + inherited.getSimpleSignature(),
                            sourceElement, nameFeature(sourceElement), OVERRIDE_REDUCES_VISIBILITY);
                } else if (details.contains(OverrideCheckDetails.EXCEPTION_MISMATCH)) {
                    if (exceptionMismatch == null)
                        exceptionMismatch = Lists.newArrayListWithCapacity(allInherited.size());
                    exceptionMismatch.add(inherited);
                } else if (details.contains(OverrideCheckDetails.RETURN_MISMATCH)) {
                    error("The return type is incompatible with " + inherited.getSimpleSignature(), sourceElement,
                            returnTypeFeature(sourceElement), INCOMPATIBLE_RETURN_TYPE);
                } else if (details.contains(OverrideCheckDetails.SYNCHRONIZED_MISMATCH)) {
                    warning("The overridden method is synchronized, the current one is not synchronized.",
                            sourceElement, nameFeature(sourceElement), MISSING_SYNCHRONIZED);
                }
            }
        }
        if (exceptionMismatch != null) {
            createExceptionMismatchError(resolved, sourceElement, exceptionMismatch);
        }
        if (sourceElement instanceof XtendFunction) {
            XtendFunction function = (XtendFunction) sourceElement;
            if (!overrideProblems && !function.isOverride() && !function.isStatic()) {
                error("The method " + resolved.getSimpleSignature() + " of type " + getDeclaratorName(resolved)
                        + " must use override keyword since it actually overrides a supertype method.", function,
                        XTEND_FUNCTION__NAME, MISSING_OVERRIDE);
            }
            if (!overrideProblems && function.isOverride() && function.isStatic()) {
                for (IResolvedOperation inherited : allInherited) {
                    error("The method " + resolved.getSimpleSignature() + " of type " + getDeclaratorName(resolved)
                            + " shadows the method " + resolved.getSimpleSignature() + " of type "
                            + getDeclaratorName(inherited) + ", but does not override it.", function,
                            XTEND_FUNCTION__NAME, function.getModifiers().indexOf("override"), OBSOLETE_OVERRIDE);
                }
            }
            if (function.isOverride()) {
                for (XAnnotation anno : function.getAnnotations()) {
                    if (anno != null && anno.getAnnotationType() != null
                            && Override.class.getName().equals(anno.getAnnotationType().getIdentifier())) {
                        warning("Superfluous @Override annotation", anno, null, OBSOLETE_ANNOTATION_OVERRIDE);
                    }
                }
            }
        }
    }

    protected String getDeclaratorName(IResolvedOperation resolved) {
        return getDeclaratorName(resolved.getDeclaration());
    }

    protected void createExceptionMismatchError(IResolvedOperation operation, EObject sourceElement,
            List<IResolvedOperation> exceptionMismatch) {
        List<LightweightTypeReference> exceptions = operation.getIllegallyDeclaredExceptions();
        StringBuilder message = new StringBuilder(100);
        message.append("The declared exception");
        if (exceptions.size() > 1) {
            message.append('s');
        }
        message.append(' ');
        for (int i = 0; i < exceptions.size(); i++) {
            if (i != 0) {
                if (i != exceptions.size() - 1)
                    message.append(", ");
                else
                    message.append(" and ");
            }
            message.append(exceptions.get(i).getHumanReadableName());
        }
        if (exceptions.size() > 1) {
            message.append(" are");
        } else {
            message.append(" is");
        }
        message.append(" not compatible with throws clause in ");
        for (int i = 0; i < exceptionMismatch.size(); i++) {
            if (i != 0) {
                if (i != exceptionMismatch.size() - 1)
                    message.append(", ");
                else
                    message.append(" and ");
            }
            IResolvedOperation resolvedOperation = exceptionMismatch.get(i);
            message.append(getDeclaratorName(resolvedOperation));
            message.append('.');
            message.append(exceptionMismatch.get(i).getSimpleSignature());
        }
        error(message.toString(), sourceElement, exceptionsFeature(sourceElement), INCOMPATIBLE_THROWS_CLAUSE);
    }

    protected EObject findPrimarySourceElement(IResolvedOperation operation) {
        return associations.getPrimarySourceElement(operation.getDeclaration());
    }

    protected boolean isMorePrivateThan(JvmVisibility o1, JvmVisibility o2) {
        if (o1 == o2) {
            return false;
        } else {
            switch (o1) {
            case DEFAULT:
                return o2 != JvmVisibility.PRIVATE;
            case PRIVATE:
                return true;
            case PROTECTED:
                return o2 == JvmVisibility.PUBLIC;
            case PUBLIC:
                return false;
            default:
                throw new IllegalArgumentException("Unknown JvmVisibility " + o1);
            }
        }
    }

    @Check
    public void checkDefaultSuperConstructor(XtendClass xtendClass) {
        JvmGenericType inferredType = associations.getInferredType(xtendClass);
        if (inferredType == null)
            return;
        Iterable<JvmConstructor> constructors = filter(inferredType.getMembers(), JvmConstructor.class);
        if (inferredType.getExtendedClass() != null) {
            JvmType superType = inferredType.getExtendedClass().getType();
            if (superType instanceof JvmGenericType) {
                Iterable<JvmConstructor> superConstructors = ((JvmGenericType) superType).getDeclaredConstructors();
                for (JvmConstructor superConstructor : superConstructors) {
                    if (superConstructor.getParameters().isEmpty())
                        // there is a default super constructor. nothing more to check
                        return;
                }
                if (size(constructors) == 1
                        && typeExtensions.isSingleSyntheticDefaultConstructor(constructors.iterator().next())) {
                    List<String> issueData = newArrayList();
                    for (JvmConstructor superConstructor : superConstructors) {
                        issueData.add(EcoreUtil.getURI(superConstructor).toString());
                        issueData.add(
                                doGetReadableSignature(xtendClass.getName(), superConstructor.getParameters()));
                    }
                    error("No default constructor in super type " + superType.getSimpleName() + "."
                            + xtendClass.getName() + " must define an explicit constructor.", xtendClass,
                            XTEND_TYPE_DECLARATION__NAME, MISSING_CONSTRUCTOR, toArray(issueData, String.class));
                } else {
                    for (JvmConstructor constructor : constructors) {
                        XExpression expression = containerProvider.getAssociatedExpression(constructor);
                        if (expression instanceof XBlockExpression) {
                            List<XExpression> expressions = ((XBlockExpression) expression).getExpressions();
                            if (expressions.isEmpty() || !isDelegateConstructorCall(expressions.get(0))) {
                                EObject source = associations.getPrimarySourceElement(constructor);
                                error("No default constructor in super type " + superType.getSimpleName()
                                        + ". Another constructor must be invoked explicitly.", source, null,
                                        MUST_INVOKE_SUPER_CONSTRUCTOR);
                            }
                        }
                    }
                }
            }
        }
    }

    protected boolean isDelegateConstructorCall(XExpression expression) {
        if (expression instanceof XFeatureCall) {
            JvmIdentifiableElement feature = ((XFeatureCall) expression).getFeature();
            return (feature != null && !feature.eIsProxy() && feature instanceof JvmConstructor);
        }
        return false;
    }

    protected boolean isInterface(JvmDeclaredType type) {
        return type instanceof JvmGenericType && ((JvmGenericType) type).isInterface();
    }

    protected String canonicalName(JvmIdentifiableElement element) {
        return (element != null) ? notNull(element.getIdentifier()) : null;
    }

    protected String getReadableSignature(JvmExecutable executable) {
        if (executable == null)
            return "null";
        return doGetReadableSignature(executable.getSimpleName(), executable.getParameters());
    }

    protected String doGetReadableSignature(String simpleName, List<JvmFormalParameter> parameters) {
        return getReadableSignature(simpleName,
                Lists.transform(parameters, new Function<JvmFormalParameter, JvmTypeReference>() {
                    @Override
                    public JvmTypeReference apply(JvmFormalParameter from) {
                        return from.getParameterType();
                    }
                }));
    }

    protected String getReadableSignature(String elementName, List<JvmTypeReference> parameterTypes) {
        StringBuilder result = new StringBuilder(elementName);
        result.append('(');
        for (int i = 0; i < parameterTypes.size(); i++) {
            if (i != 0) {
                result.append(", ");
            }
            JvmTypeReference parameterType = parameterTypes.get(i);
            if (parameterType != null)
                result.append(parameterType.getSimpleName());
            else
                result.append("null");
        }
        result.append(')');
        return result.toString();
    }

    @Check
    public void checkParameterNames(XtendFunction function) {
        for (int i = 0; i < function.getParameters().size(); ++i) {
            String leftParameterName = function.getParameters().get(i).getName();
            for (int j = i + 1; j < function.getParameters().size(); ++j) {
                if (equal(leftParameterName, function.getParameters().get(j).getName())) {
                    error("Duplicate parameter " + leftParameterName, XTEND_EXECUTABLE__PARAMETERS, i,
                            DUPLICATE_PARAMETER_NAME);
                    error("Duplicate parameter " + leftParameterName, XTEND_EXECUTABLE__PARAMETERS, j,
                            DUPLICATE_PARAMETER_NAME);
                }
            }
            if (function.getCreateExtensionInfo() != null) {
                if (equal(leftParameterName, function.getCreateExtensionInfo().getName())) {
                    error("Duplicate parameter " + leftParameterName, XTEND_EXECUTABLE__PARAMETERS, i,
                            DUPLICATE_PARAMETER_NAME);
                    if (function.getCreateExtensionInfo().eIsSet(CREATE_EXTENSION_INFO__NAME))
                        error("Duplicate parameter " + leftParameterName, function.getCreateExtensionInfo(),
                                CREATE_EXTENSION_INFO__NAME, DUPLICATE_PARAMETER_NAME);
                    else
                        error("Duplicate implicit parameter 'it'", function.getCreateExtensionInfo(),
                                CREATE_EXTENSION_INFO__NAME, DUPLICATE_PARAMETER_NAME);
                }
            }
        }
    }

    @Check
    public void checkAbstract(XtendFunction function) {
        XtendTypeDeclaration declarator = function.getDeclaringType();
        if (function.getExpression() == null) {
            if (declarator instanceof XtendClass || declarator.isAnonymous()) {
                if (function.isDispatch()) {
                    error("The dispatch method " + function.getName() + " in type "
                            + localClassAwareTypeNames.getReadableName(declarator) + " must not be abstract",
                            XTEND_FUNCTION__NAME, -1, DISPATCH_FUNCTIONS_MUST_NOT_BE_ABSTRACT);
                    return;
                }
                if (function.getCreateExtensionInfo() != null) {
                    error("The 'create'-method " + function.getName() + " in type "
                            + localClassAwareTypeNames.getReadableName(declarator) + " must not be abstract",
                            XTEND_FUNCTION__NAME, -1, CREATE_FUNCTIONS_MUST_NOT_BE_ABSTRACT);
                    return;
                }
                if (declarator.isAnonymous()) {
                    error("The abstract method " + function.getName() + " in type "
                            + localClassAwareTypeNames.getReadableName(declarator)
                            + " can only be defined by an abstract class.", XTEND_FUNCTION__NAME, -1,
                            MISSING_ABSTRACT_IN_ANONYMOUS);
                } else if (!((XtendClass) declarator).isAbstract() && !function.isNative()) {
                    error("The abstract method " + function.getName() + " in type "
                            + localClassAwareTypeNames.getReadableName(declarator)
                            + " can only be defined by an abstract class.", XTEND_FUNCTION__NAME, -1,
                            MISSING_ABSTRACT);
                }
                if (function.getReturnType() == null && !function.isOverride()) {
                    error("The " + (function.isNative() ? "native" : "abstract") + " method " + function.getName()
                            + " in type " + localClassAwareTypeNames.getReadableName(declarator)
                            + " must declare a return type", XTEND_FUNCTION__NAME, -1,
                            ABSTRACT_METHOD_MISSING_RETURN_TYPE);
                }
            } else if (declarator instanceof XtendInterface) {
                if (function.getCreateExtensionInfo() != null) {
                    error("'Create'-method " + function.getName() + " is not permitted in an interface",
                            XTEND_FUNCTION__NAME, -1, CREATE_FUNCTIONS_MUST_NOT_BE_ABSTRACT);
                    return;
                }
                if (function.getReturnType() == null && !function.isOverride()) {
                    error("The abstract method " + function.getName() + " in type "
                            + localClassAwareTypeNames.getReadableName(declarator) + " must declare a return type",
                            XTEND_FUNCTION__NAME, -1, ABSTRACT_METHOD_MISSING_RETURN_TYPE);
                }
            }
        } else if (declarator instanceof XtendInterface) {
            if (!getGeneratorConfig(function).getJavaSourceVersion().isAtLeast(JAVA8)) {
                error("Abstract methods do not specify a body", XTEND_FUNCTION__NAME, -1,
                        ABSTRACT_METHOD_WITH_BODY);
            }
        }
    }

    @Check
    public void checkOperatorSignature(XtendFunction function) {
        String functionName = function.getName();
        if (functionName != null) {
            QualifiedName qualifiedName = QualifiedName.create(functionName);
            QualifiedName operator = operatorMapping.getOperator(qualifiedName);
            if (operator != null) {
                JvmOperation operation = associations.getDirectlyInferredOperation(function);
                if (operation != null) {
                    int parameterSize = operation.getParameters().size();
                    if (function.isStatic()) {
                        if (operatorMapping.isUnaryOperator(operator)
                                && operatorMapping.isBinaryOperator(operator)) {
                            if (parameterSize < 1) {
                                addIssue("The static operator '" + operator + "' requires at least one argument.",
                                        function, XtendPackage.Literals.XTEND_FUNCTION__NAME,
                                        INVALID_OPERATOR_SIGNATURE);
                            } else if (parameterSize > 2) {
                                addIssue("The static operator '" + operator + "' allows at most two arguments.",
                                        function, XtendPackage.Literals.XTEND_FUNCTION__NAME,
                                        INVALID_OPERATOR_SIGNATURE);
                            }
                        } else if (operatorMapping.isUnaryOperator(operator)) {
                            if (parameterSize != 1) {
                                addIssue(
                                        "The static unary operator '" + operator
                                                + "' requires exactly one argument.",
                                        function, XtendPackage.Literals.XTEND_FUNCTION__NAME,
                                        INVALID_OPERATOR_SIGNATURE);
                            }
                        } else if (parameterSize != 2) {
                            addIssue(
                                    "The static binary operator '" + operator + "' requires exactly two arguments.",
                                    function, XtendPackage.Literals.XTEND_FUNCTION__NAME,
                                    INVALID_OPERATOR_SIGNATURE);
                        }
                    } else {
                        if (operatorMapping.isUnaryOperator(operator)
                                && operatorMapping.isBinaryOperator(operator)) {
                            if (parameterSize > 2) {
                                addIssue("The operator '" + operator + "' allows at most two arguments.", function,
                                        XtendPackage.Literals.XTEND_FUNCTION__NAME, INVALID_OPERATOR_SIGNATURE);
                            }
                        } else if (operatorMapping.isUnaryOperator(operator)) {
                            if (parameterSize > 1) {
                                addIssue("The unary operator '" + operator + "' allows at most one argument.",
                                        function, XtendPackage.Literals.XTEND_FUNCTION__NAME,
                                        INVALID_OPERATOR_SIGNATURE);
                            }
                        } else if (parameterSize == 0) {
                            addIssue("The binary operator '" + operator + "' requires at least one argument.",
                                    function, XtendPackage.Literals.XTEND_FUNCTION__NAME,
                                    INVALID_OPERATOR_SIGNATURE);

                        } else if (parameterSize > 2) {
                            addIssue("The binary operator '" + operator + "' allows at most two arguments.",
                                    function, XtendPackage.Literals.XTEND_FUNCTION__NAME,
                                    INVALID_OPERATOR_SIGNATURE);
                        }
                    }
                }
            }
        }
    }

    @Check
    public void checkParameterNames(XtendConstructor constructor) {
        for (int i = 0; i < constructor.getParameters().size(); ++i) {
            String leftParameterName = constructor.getParameters().get(i).getName();
            for (int j = i + 1; j < constructor.getParameters().size(); ++j) {
                if (equal(leftParameterName, constructor.getParameters().get(j).getName())) {
                    error("Duplicate parameter " + leftParameterName, XTEND_EXECUTABLE__PARAMETERS, i,
                            DUPLICATE_PARAMETER_NAME);
                    error("Duplicate parameter " + leftParameterName, XTEND_EXECUTABLE__PARAMETERS, j,
                            DUPLICATE_PARAMETER_NAME);
                }
            }
        }
    }

    @Check
    public void dispatchFuncWithTypeParams(XtendFunction func) {
        if (func.isDispatch()) {
            if (func.getParameters().isEmpty()) {
                error("A dispatch method must at least have one parameter declared.", func, XTEND_MEMBER__MODIFIERS,
                        func.getModifiers().indexOf("dispatch"), IssueCodes.DISPATCH_FUNC_WITHOUT_PARAMS);
            }
            if (!func.getTypeParameters().isEmpty()) {
                error("A dispatch method must not declare any type parameters.", func, XTEND_MEMBER__MODIFIERS,
                        func.getModifiers().indexOf("dispatch"), IssueCodes.DISPATCH_FUNC_WITH_TYPE_PARAMS);
            }
            if (func.getName().startsWith("_")) {
                error("A dispatch method's name must not start with an underscore.", func, XTEND_FUNCTION__NAME,
                        IssueCodes.DISPATCH_FUNC_NAME_STARTS_WITH_UNDERSCORE);
            }
        }
    }

    @Check
    public void checkDispatchFunctions(XtendClass clazz) {
        JvmGenericType type = associations.getInferredType(clazz);
        if (type != null) {
            Multimap<DispatchHelper.DispatchSignature, JvmOperation> dispatchMethods = dispatchHelper
                    .getDeclaredOrEnhancedDispatchMethods(type);
            checkDispatchNonDispatchConflict(clazz, dispatchMethods);
            for (DispatchHelper.DispatchSignature signature : dispatchMethods.keySet()) {
                Collection<JvmOperation> dispatchOperations = dispatchMethods.get(signature);
                JvmOperation syntheticDispatchMethod = dispatchHelper.getDispatcherOperation(type, signature);
                if (syntheticDispatchMethod != null) {
                    JvmOperation overriddenOperation = overrideHelper
                            .findOverriddenOperation(syntheticDispatchMethod);
                    Boolean expectStatic = null;
                    if (overriddenOperation != null) {
                        if (isMorePrivateThan(syntheticDispatchMethod.getVisibility(),
                                overriddenOperation.getVisibility())) {
                            String msg = "Synthetic dispatch method reduces visibility of overridden method "
                                    + overriddenOperation.getIdentifier();
                            addDispatchError(type, dispatchOperations, msg, null, OVERRIDE_REDUCES_VISIBILITY);
                        }
                        expectStatic = overriddenOperation.isStatic();
                    }
                    LightweightTypeReference dispatchMethodReturnType = getActualType(clazz,
                            syntheticDispatchMethod);
                    if (dispatchOperations.size() == 1) {
                        JvmOperation singleOp = dispatchOperations.iterator().next();
                        XtendFunction function = associations.getXtendFunction(singleOp);
                        addIssue("Single dispatch method.", function, XTEND_MEMBER__MODIFIERS,
                                function.getModifiers().indexOf("dispatch"), SINGLE_DISPATCH_FUNCTION);
                    } else {
                        Multimap<List<JvmType>, JvmOperation> signatures = HashMultimap.create();
                        boolean[] allPrimitive = new boolean[signature.getArity()];
                        Arrays.fill(allPrimitive, true);
                        boolean isFirstLocalOperation = true;
                        JvmVisibility commonVisibility = null;
                        Boolean commonStatic = null;
                        for (JvmOperation jvmOperation : dispatchOperations) {
                            signatures.put(getParamTypes(jvmOperation, true), jvmOperation);
                            for (int i = 0; i < jvmOperation.getParameters().size(); i++) {
                                JvmFormalParameter parameter = jvmOperation.getParameters().get(i);
                                if (!(parameter.getParameterType().getType() instanceof JvmPrimitiveType)) {
                                    allPrimitive[i] = false;
                                }
                            }
                            if (jvmOperation.getDeclaringType() == type) {
                                if (expectStatic != null) {
                                    if (expectStatic && !jvmOperation.isStatic()) {
                                        String msg = "The dispatch method must be static because the dispatch methods in the superclass are static.";
                                        addDispatchError(jvmOperation, msg, "static",
                                                DISPATCH_FUNCTIONS_STATIC_EXPECTED);
                                    }
                                    if (!expectStatic && jvmOperation.isStatic()) {
                                        String msg = "The dispatch method must not be static because the dispatch methods in the superclass are not static.";
                                        addDispatchError(jvmOperation, msg, "static",
                                                DISPATCH_FUNCTIONS_NON_STATIC_EXPECTED);
                                    }
                                }
                                if (isFirstLocalOperation) {
                                    commonVisibility = jvmOperation.getVisibility();
                                    commonStatic = jvmOperation.isStatic();
                                    isFirstLocalOperation = false;
                                } else {
                                    if (jvmOperation.getVisibility() != commonVisibility) {
                                        commonVisibility = null;
                                    }
                                    if (commonStatic != null && commonStatic != jvmOperation.isStatic()) {
                                        commonStatic = null;
                                    }
                                }
                                // TODO move validation to type computation
                                if (dispatchMethodReturnType != null) {
                                    XtendFunction function = associations.getXtendFunction(jvmOperation);
                                    if (function != null) {
                                        LightweightTypeReference operationType = getActualType(
                                                function.getExpression(), jvmOperation);
                                        if (!dispatchMethodReturnType.isAssignableFrom(operationType)) {
                                            error("Incompatible return type of dispatch method. Expected "
                                                    + dispatchMethodReturnType.getHumanReadableName() + " but was "
                                                    + operationType.getHumanReadableName(), function,
                                                    XtendPackage.Literals.XTEND_FUNCTION__RETURN_TYPE,
                                                    ValidationMessageAcceptor.INSIGNIFICANT_INDEX,
                                                    INCOMPATIBLE_RETURN_TYPE);
                                        }
                                    }
                                }
                            }
                        }
                        if (commonVisibility == null) {
                            addDispatchError(type, dispatchOperations,
                                    "All local dispatch methods must have the same visibility.", null,
                                    DISPATCH_FUNCTIONS_WITH_DIFFERENT_VISIBILITY);
                        }
                        if (expectStatic == null && commonStatic == null) {
                            addDispatchError(type, dispatchOperations,
                                    "Static and non-static dispatch methods can not be mixed.", "static",
                                    DISPATCH_FUNCTIONS_MIXED_STATIC_AND_NON_STATIC);
                        }
                        for (final List<JvmType> paramTypes : signatures.keySet()) {
                            Collection<JvmOperation> ops = signatures.get(paramTypes);
                            if (ops.size() > 1) {
                                if (Iterables.any(ops, new Predicate<JvmOperation>() {
                                    @Override
                                    public boolean apply(JvmOperation input) {
                                        return !getParamTypes(input, false).equals(paramTypes);
                                    }
                                })) {
                                    for (JvmOperation jvmOperation : ops) {
                                        XtendFunction function = associations.getXtendFunction(jvmOperation);
                                        error("Duplicate dispatch methods. Primitives cannot overload their wrapper types in dispatch methods.",
                                                function, null, DUPLICATE_METHOD);
                                    }
                                }
                            }
                        }
                        for (int i = 0; i < allPrimitive.length; i++) {
                            if (allPrimitive[i]) {
                                Iterator<JvmOperation> operationIter = dispatchOperations.iterator();
                                JvmType paramType1 = operationIter.next().getParameters().get(i).getParameterType()
                                        .getType();
                                while (operationIter.hasNext()) {
                                    JvmType paramType2 = operationIter.next().getParameters().get(i)
                                            .getParameterType().getType();
                                    if (!paramType2.equals(paramType1)) {
                                        for (JvmOperation jvmOperation : dispatchOperations) {
                                            XtendFunction function = associations.getXtendFunction(jvmOperation);
                                            addIssue(
                                                    "Dispatch methods have arguments with different primitive types.",
                                                    function, XTEND_EXECUTABLE__PARAMETERS, i,
                                                    DISPATCH_FUNCTIONS_DIFFERENT_PRIMITIVE_ARGS);
                                        }
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    protected void checkDispatchNonDispatchConflict(XtendClass clazz,
            Multimap<DispatchHelper.DispatchSignature, JvmOperation> dispatchMethods) {
        if (isIgnored(DISPATCH_PLAIN_FUNCTION_NAME_CLASH)) {
            return;
        }
        Multimap<DispatchHelper.DispatchSignature, XtendFunction> nonDispatchMethods = HashMultimap.create();
        for (XtendFunction method : filter(clazz.getMembers(), XtendFunction.class)) {
            if (!method.isDispatch()) {
                nonDispatchMethods.put(
                        new DispatchHelper.DispatchSignature(method.getName(), method.getParameters().size()),
                        method);
            }
        }
        for (DispatchHelper.DispatchSignature dispatchSignature : dispatchMethods.keySet()) {
            if (nonDispatchMethods.containsKey(dispatchSignature)) {
                for (XtendFunction function : nonDispatchMethods.get(dispatchSignature))
                    addIssue("Non-dispatch method has same name and number of parameters as dispatch method",
                            function, XTEND_FUNCTION__NAME, DISPATCH_PLAIN_FUNCTION_NAME_CLASH);
                for (JvmOperation operation : dispatchMethods.get(dispatchSignature))
                    addIssue("Dispatch method has same name and number of parameters as non-dispatch method",
                            associations.getXtendFunction(operation), XTEND_FUNCTION__NAME,
                            DISPATCH_PLAIN_FUNCTION_NAME_CLASH);
            }
        }
    }

    protected void addDispatchError(JvmGenericType type, Iterable<JvmOperation> operations, String message,
            String modifier, String ISSUE_ID) {
        for (JvmOperation jvmOperation : operations)
            if (jvmOperation.getDeclaringType() == type)
                addDispatchError(jvmOperation, message, modifier, ISSUE_ID);
    }

    protected void addDispatchError(JvmOperation jvmOperation, String message, String modifier, String ISSUE_ID) {
        XtendFunction function = associations.getXtendFunction(jvmOperation);
        if (function != null) {
            int modifierIndex = -1;
            if (modifier != null) {
                modifierIndex = function.getModifiers().indexOf(modifier);
            } else {
                for (int i = 0; i < function.getModifiers().size(); ++i) {
                    if (visibilityModifers.contains(function.getModifiers().get(i))) {
                        modifierIndex = i;
                        break;
                    }
                }
            }
            if (modifierIndex == -1)
                modifierIndex = function.getModifiers().indexOf("dispatch");
            error(message, function, XTEND_MEMBER__MODIFIERS, modifierIndex, ISSUE_ID);
        }
    }

    protected List<JvmType> getParamTypes(JvmOperation jvmOperation, boolean wrapPrimitives) {
        List<JvmType> types = newArrayList();
        for (JvmFormalParameter p : jvmOperation.getParameters()) {
            LightweightTypeReference typeReference = toLightweightTypeReference(p.getParameterType());
            if (wrapPrimitives) {
                typeReference = typeReference.getWrapperTypeIfPrimitive();
            }
            types.add(typeReference.getType());
        }
        return types;
    }

    @Check
    public void checkNoReturnsInCreateExtensions(XtendFunction func) {
        if (func.getCreateExtensionInfo() == null)
            return;
        List<XReturnExpression> found = newArrayList();
        collectReturnExpressions(func.getCreateExtensionInfo().getCreateExpression(), found);
        for (XReturnExpression xReturnExpression : found) {
            error("Return is not allowed in creation expression", xReturnExpression, null, INVALID_EARLY_EXIT);
        }
    }

    @Check
    public void checkCreateFunctionIsNotTypeVoid(XtendFunction func) {
        if (func.getCreateExtensionInfo() == null)
            return;
        if (func.getReturnType() == null) {
            JvmOperation operation = associations.getDirectlyInferredOperation(func);
            if (operation != null && isPrimitiveVoid(operation.getReturnType())) {
                error("void is an invalid type for the create method " + func.getName(), func,
                        XtendPackage.Literals.XTEND_FUNCTION__NAME, INVALID_USE_OF_TYPE);
            }
        } else if (isPrimitiveVoid(func.getReturnType())) {
            if (func.getReturnType() != null)
                error("Create method " + func.getName() + " may not declare return type void.",
                        func.getReturnType(), null, INVALID_USE_OF_TYPE);
            else
                error("The inherited return type void of " + func.getName() + " is invalid for create method.",
                        func.getReturnType(), null, INVALID_USE_OF_TYPE);
        }
    }

    @Check
    public void checkCreateFunctionIsNotGeneric(XtendFunction func) {
        if (func.getCreateExtensionInfo() == null)
            return;
        if (!func.getTypeParameters().isEmpty())
            error("Create methods can not have type parameters.", func, XTEND_MEMBER__MODIFIERS,
                    func.getModifiers().indexOf("static"), INVALID_USE_OF_STATIC);
    }

    @Check
    public void checkCreateFunctionIsNotStatic(XtendFunction func) {
        if (func.getCreateExtensionInfo() == null)
            return;
        if (func.isStatic())
            error("Create methods can not be static.", func, XTEND_MEMBER__MODIFIERS,
                    func.getModifiers().indexOf("static"), INVALID_USE_OF_STATIC);
    }

    @Override
    protected boolean isValueExpectedRecursive(XExpression expr) {
        EObject container = expr.eContainer();
        if (container instanceof RichString || container instanceof RichStringForLoop
                || container instanceof XtendField) {
            return true;
        }
        return super.isValueExpectedRecursive(expr);
    }

    protected void collectReturnExpressions(EObject expr, List<XReturnExpression> found) {
        if (expr instanceof XReturnExpression) {
            found.add((XReturnExpression) expr);
        } else if (expr instanceof XClosure) {
            return;
        }
        for (EObject child : expr.eContents()) {
            collectReturnExpressions(child, found);
        }
    }

    @Check
    public void checkClasses(XtendFile file) {
        //TODO this check should not be file local, but instead check for any other sources which might declare a
        // java type with the same name. Also this then belongs to Xbase and should be defined on JvmDeclaredType
        Set<String> names = newLinkedHashSet();
        for (XtendTypeDeclaration clazz : file.getXtendTypes()) {
            if (clazz.getName() != null && !names.add(clazz.getName()))
                error("The type " + clazz.getName() + " is already defined.", clazz,
                        XtendPackage.Literals.XTEND_TYPE_DECLARATION__NAME, -1, IssueCodes.DUPLICATE_TYPE_NAME);
        }
    }

    public boolean doCheckValidMemberName(XtendMember member) {
        EStructuralFeature nameAttribute = member.eClass().getEStructuralFeature("name");
        if (nameAttribute != null) {
            String name = (String) member.eGet(nameAttribute);
            if (name != null && (name.equals("this") || name.equals("it"))) {
                error("'it' and 'this' are not allowed as member names", nameAttribute, INVALID_MEMBER_NAME);
                return false;
            }
        }
        return true;
    }

    @Check
    public void checkLocalUsageOfDeclaredFields(XtendField field) {
        if (doCheckValidMemberName(field) && !isIgnored(UNUSED_PRIVATE_MEMBER)) {
            JvmField jvmField = associations.getJvmField(field);
            if (jvmField == null || jvmField.getVisibility() != JvmVisibility.PRIVATE
                    || jvmField.eContainer() == null)
                return;
            if (isLocallyUsed(jvmField, getOutermostType(field)))
                return;
            String message;
            if (field.isExtension()) {
                if (field.getName() == null && jvmField.getType() != null)
                    message = "The extension " + jvmField.getType().getIdentifier() + " is not used in "
                            + getDeclaratorName(jvmField);
                else
                    message = "The extension " + getDeclaratorName(jvmField) + "." + jvmField.getSimpleName()
                            + " is not used";
            } else {
                message = "The value of the field " + getDeclaratorName(jvmField) + "." + jvmField.getSimpleName()
                        + " is not used";
            }
            addIssueToState(UNUSED_PRIVATE_MEMBER, message, XtendPackage.Literals.XTEND_FIELD__NAME);
        }
    }

    /** Replies the outer-most type of the given member.
     * @return the container of {@code XtendTypeDeclaration} type if it exists, or the direct container.
     */
    protected final EObject getOutermostType(XtendMember member) {
        XtendTypeDeclaration result = EcoreUtil2.getContainerOfType(member, XtendTypeDeclaration.class);
        if (result == null) {
            return member.eContainer();
        }
        while (!(result.eContainer() instanceof XtendFile)) {
            XtendTypeDeclaration next = EcoreUtil2.getContainerOfType(result.eContainer(),
                    XtendTypeDeclaration.class);
            if (next == null) {
                return result;
            }
            result = next;
        }
        return result;
    }

    protected String getDeclaratorName(JvmFeature feature) {
        JvmDeclaredType declarator = feature.getDeclaringType();
        if (declarator.isLocal()) {
            return "new " + Iterables.getLast(declarator.getSuperTypes()).getType().getSimpleName() + "(){}";
        } else {
            return declarator.getSimpleName();
        }
    }

    @Check
    public void checkLocalUsageOfDeclaredXtendFunction(XtendFunction function) {
        if (doCheckValidMemberName(function) && !isIgnored(UNUSED_PRIVATE_MEMBER)) {
            JvmOperation jvmOperation = function.isDispatch() ? associations.getDispatchOperation(function)
                    : associations.getDirectlyInferredOperation(function);
            if (jvmOperation != null && jvmOperation.getVisibility() == JvmVisibility.PRIVATE
                    && !isLocallyUsed(jvmOperation, getOutermostType(function))) {
                String message = "The method " + jvmOperation.getSimpleName() + uiStrings.parameters(jvmOperation)
                        + " from the type " + getDeclaratorName(jvmOperation) + " is never used locally.";
                addIssueToState(UNUSED_PRIVATE_MEMBER, message, XtendPackage.Literals.XTEND_FUNCTION__NAME);
            }
        }
    }

    @Check
    public void checkDeclaredExceptions(XtendConstructor constructor) {
        JvmConstructor jvmConstructor = associations.getInferredConstructor(constructor);
        if (jvmConstructor != null) {
            checkExceptions(constructor, jvmConstructor.getExceptions(),
                    XtendPackage.Literals.XTEND_EXECUTABLE__EXCEPTIONS);
        }
    }

    @Check
    public void checkTypeParameterForwardReferences(XtendClass xtendClass) {
        doCheckTypeParameterForwardReference(xtendClass.getTypeParameters());
    }

    @Check
    public void checkTypeParameterForwardReferences(XtendInterface xtendInterface) {
        doCheckTypeParameterForwardReference(xtendInterface.getTypeParameters());
    }

    @Check
    public void checkTypeParameterForwardReferences(XtendFunction xtendFunction) {
        doCheckTypeParameterForwardReference(xtendFunction.getTypeParameters());
    }

    @Check
    public void checkTypeParametersAreUnsupported(XtendConstructor constructor) {
        if (!constructor.getTypeParameters().isEmpty()) {
            error("Type parameters are not supported for constructors",
                    XtendPackage.Literals.XTEND_EXECUTABLE__TYPE_PARAMETERS, INSIGNIFICANT_INDEX,
                    CONSTRUCTOR_TYPE_PARAMS_NOT_SUPPORTED);
        }
    }

    @Check
    public void checkDeclaredExceptions(XtendFunction function) {
        JvmOperation jvmOperation = associations.getDirectlyInferredOperation(function);
        if (jvmOperation != null) {
            checkExceptions(function, jvmOperation.getExceptions(),
                    XtendPackage.Literals.XTEND_EXECUTABLE__EXCEPTIONS);
        }
    }

    private void checkExceptions(EObject context, List<JvmTypeReference> exceptions, EReference reference) {
        Set<String> declaredExceptionNames = Sets.newHashSet();
        JvmTypeReference throwableType = getServices().getTypeReferences().getTypeForName(Throwable.class, context);
        if (throwableType == null) {
            return;
        }
        ITypeReferenceOwner owner = new StandardTypeReferenceOwner(getServices(), context);
        LightweightTypeReference throwableReference = owner.toLightweightTypeReference(throwableType);
        for (int i = 0; i < exceptions.size(); i++) {
            JvmTypeReference exception = exceptions.get(i);
            // throwables may not carry generics thus the raw comparison is sufficient
            if (exception.getType() != null && !exception.getType().eIsProxy()) {
                if (!throwableReference.isAssignableFrom(exception.getType()))
                    error("No exception of type " + exception.getSimpleName()
                            + " can be thrown; an exception type must be a subclass of Throwable", reference, i,
                            EXCEPTION_NOT_THROWABLE);
                if (!declaredExceptionNames.add(exception.getQualifiedName()))
                    error("Exception " + exception.getSimpleName() + " is declared twice", reference, i,
                            EXCEPTION_DECLARED_TWICE);
            }
        }
    }

    @Check
    public void checkLeftHandSideIsVariable(XAssignment assignment) {
        String concreteSyntaxFeatureName = assignment.getConcreteSyntaxFeatureName();
        if (concreteSyntaxFeatureName.equals(IFeatureNames.THIS.toString()))
            error("Left-hand side of an assignment must be an variable",
                    XbasePackage.Literals.XABSTRACT_FEATURE_CALL__FEATURE, LEFT_HAND_SIDE_MUST_BE_VARIABLE);
    }

    @Check
    public void checkJavaKeywordConflict(XtendField member) {
        checkNoJavaKeyword(member, XtendPackage.Literals.XTEND_FIELD__NAME);
    }

    @Check
    public void checkJavaKeywordConflict(XtendFunction member) {
        if (member.eContainer() instanceof XtendAnnotationType && "do".equals(member.getName()))
            return;
        checkNoJavaKeyword(member, XtendPackage.Literals.XTEND_FUNCTION__NAME);
        for (JvmTypeParameter p : member.getTypeParameters()) {
            checkNoJavaKeyword(p, TypesPackage.Literals.JVM_TYPE_PARAMETER__NAME);
        }
    }

    @Check
    public void checkJavaKeywordConflict(XtendConstructor member) {
        for (JvmTypeParameter p : member.getTypeParameters()) {
            checkNoJavaKeyword(p, TypesPackage.Literals.JVM_TYPE_PARAMETER__NAME);
        }
    }

    @Check
    public void checkJavaKeywordConflict(XtendTypeDeclaration member) {
        checkNoJavaKeyword(member, XtendPackage.Literals.XTEND_TYPE_DECLARATION__NAME);
    }

    @Check
    public void checkJavaKeywordConflict(XtendClass member) {
        for (JvmTypeParameter p : member.getTypeParameters()) {
            checkNoJavaKeyword(p, TypesPackage.Literals.JVM_TYPE_PARAMETER__NAME);
        }
    }

    @Check
    public void checkJavaKeywordConflict(XtendInterface member) {
        for (JvmTypeParameter p : member.getTypeParameters()) {
            checkNoJavaKeyword(p, TypesPackage.Literals.JVM_TYPE_PARAMETER__NAME);
        }
    }

    protected void checkNoJavaKeyword(EObject obj, EAttribute attribute) {
        Object name = obj.eGet(attribute);
        if (name != null) {
            if (javaUtils.isJavaKeyword(name.toString()))
                error("'" + name + "' is not a valid identifier.", obj, attribute, -1, INVALID_IDENTIFIER);
        }
    }

    @Check
    public void checkNonInitializedFieldsHaveAType(XtendField field) {
        if (field.getType() == null && field.getInitialValue() == null) {
            error("The field " + field.getName()
                    + " needs an explicit type since there is no initialization expression to infer the type from.",
                    field, XTEND_FIELD__NAME, TOO_LITTLE_TYPE_INFORMATION);
        }
    }

    @Check
    public void checkFieldsAreCalledSelf(XtendField field) {
        if ("self".equals(field.getName())) {
            addIssue("'self' is a discouraged name", field, XTEND_FIELD__NAME, VARIABLE_NAME_DISCOURAGED);
        }
    }

    @Check
    public void checkFinalFieldInitialization(XtendClass clazz) {
        JvmGenericType inferredType = associations.getInferredType(clazz);
        if (inferredType == null)
            return;
        super.checkFinalFieldInitialization(inferredType);
    }

    @Check
    public void checkFinalFieldInitialization(XtendInterface xtendInterface) {
        JvmGenericType inferredType = associations.getInferredType(xtendInterface);
        if (inferredType == null)
            return;
        super.checkFinalFieldInitialization(inferredType);
    }

    @Check
    public void checkJavaDocRefs(XtendMember member) {
        if (isIgnored(IssueCodes.JAVA_DOC_LINKING_DIAGNOSTIC))
            return;
        List<INode> documentationNodes = ((IEObjectDocumentationProviderExtension) documentationProvider)
                .getDocumentationNodes(member);
        for (INode node : documentationNodes) {
            for (ReplaceRegion region : javaDocTypeReferenceProvider.computeTypeRefRegions(node)) {
                String typeRefString = region.getText();
                if (typeRefString != null && typeRefString.length() > 0) {
                    IScope scope = scopeProvider.getScope(member,
                            TypesPackage.Literals.JVM_PARAMETERIZED_TYPE_REFERENCE__TYPE);
                    IEObjectDescription candidate = scope
                            .getSingleElement(qualifiedNameConverter.toQualifiedName(typeRefString));
                    if (candidate == null) {
                        Severity severity = getIssueSeverities(getContext(), getCurrentObject())
                                .getSeverity(IssueCodes.JAVA_DOC_LINKING_DIAGNOSTIC);
                        if (severity != null)
                            getChain().add(createDiagnostic(severity,
                                    "javaDoc: " + typeRefString + " cannot be resolved to a type", member,
                                    region.getOffset(), region.getLength(),
                                    IssueCodes.JAVA_DOC_LINKING_DIAGNOSTIC));
                    }
                }
            }
        }
    }

    @Override
    protected void reportUninitializedField(JvmField field) {
        EObject element = associations.getPrimarySourceElement(field);
        if (element instanceof XtendField) {
            error("The blank final field " + field.getSimpleName() + " may not have been initialized.", element,
                    XTEND_FIELD__NAME, FIELD_NOT_INITIALIZED);
        } else {
            error("The blank final derived field " + field.getSimpleName() + " may not have been initialized.",
                    element, null, FIELD_NOT_INITIALIZED);
        }
    }

    @Override
    protected void reportUninitializedField(JvmField field, JvmConstructor constructor) {
        EObject sourceElement = associations.getPrimarySourceElement(constructor);
        if (sourceElement != null) {
            if (associations.getXtendField(field) != null) {
                error("The blank final field " + field.getSimpleName() + " may not have been initialized.",
                        sourceElement, null, FIELD_NOT_INITIALIZED);
            } else {
                error("The blank final derived field " + field.getSimpleName() + " may not have been initialized.",
                        sourceElement, null, FIELD_NOT_INITIALIZED);
            }
        }
    }

    protected boolean hasAnnotation(Iterable<? extends XAnnotation> annotations, Class<?> annotationType) {
        for (XAnnotation anno : annotations) {
            if (anno.getAnnotationType() != null
                    && annotationType.getName().equals(anno.getAnnotationType().getIdentifier()))
                return true;
        }
        return false;
    }

    private final ModifierValidator classModifierValidator = new ModifierValidator(
            newArrayList("public", "package", "final", "abstract", "strictfp"), this);

    private final ModifierValidator interfaceModifierValidator = new ModifierValidator(
            newArrayList("public", "package", "abstract", "strictfp"), this);

    private final ModifierValidator enumModifierValidator = new ModifierValidator(newArrayList("public", "package"),
            this);

    private final ModifierValidator annotationTypeModifierValidator = new ModifierValidator(
            newArrayList("public", "package", "abstract"), this);

    private final ModifierValidator nestedClassModifierValidator = new ModifierValidator(
            newArrayList("public", "package", "protected", "private", "static", "final", "abstract", "strictfp"),
            this);

    private final ModifierValidator nestedInterfaceModifierValidator = new ModifierValidator(
            newArrayList("public", "package", "protected", "private", "static", "abstract", "strictfp"), this);

    private final ModifierValidator nestedEnumModifierValidator = new ModifierValidator(
            newArrayList("public", "package", "protected", "private", "static"), this);

    private final ModifierValidator nestedAnnotationTypeModifierValidator = new ModifierValidator(
            newArrayList("public", "package", "protected", "private", "static", "abstract"), this);

    private final ModifierValidator fieldModifierValidator = new ModifierValidator(
            newArrayList("public", "protected", "package", "private", "static", "final", "val", "var", "extension",
                    "volatile", "transient"),
            this);

    private final ModifierValidator fieldInInterfaceModifierValidator = new ModifierValidator(
            newArrayList("public", "static", "final", "val"), this);

    private final ModifierValidator constructorModifierValidator = new ModifierValidator(
            newArrayList(visibilityModifers), this);

    private final ModifierValidator methodModifierValidator = new ModifierValidator(
            newArrayList("public", "protected", "package", "private", "static", "abstract", "dispatch", "final",
                    "def", "override", "strictfp", "native", "synchronized"),
            this);

    private ModifierValidator methodInInterfaceModifierValidator;

    @Check
    protected void checkModifiers(XtendClass xtendClass) {
        EObject eContainer = xtendClass.eContainer();
        if (eContainer instanceof XtendFile) {
            classModifierValidator.checkModifiers(xtendClass, "class " + xtendClass.getName());
        } else {
            nestedClassModifierValidator.checkModifiers(xtendClass, "class " + xtendClass.getName());
            // TODO remove this constraint
            if (!xtendClass.isStatic()) {
                if (eContainer instanceof XtendClass) { // types in interfaces and annotations are implicitely static
                    error("Nested classes must be static", XTEND_TYPE_DECLARATION__NAME, -1,
                            MISSING_STATIC_MODIFIER);
                }
            }
        }
    }

    @Check
    protected void checkModifiers(XtendInterface xtendInterface) {
        EObject eContainer = xtendInterface.eContainer();
        if (eContainer instanceof XtendFile) {
            interfaceModifierValidator.checkModifiers(xtendInterface, "interface " + xtendInterface.getName());
        } else {
            nestedInterfaceModifierValidator.checkModifiers(xtendInterface,
                    "interface " + xtendInterface.getName());
        }
    }

    @Check
    protected void checkModifiers(XtendEnum xtendEnum) {
        EObject eContainer = xtendEnum.eContainer();
        if (eContainer instanceof XtendFile) {
            enumModifierValidator.checkModifiers(xtendEnum, "enum " + xtendEnum.getName());
        } else {
            nestedEnumModifierValidator.checkModifiers(xtendEnum, "enum " + xtendEnum.getName());
        }
    }

    @Check
    protected void checkModifiers(XtendAnnotationType annotation) {
        EObject eContainer = annotation.eContainer();
        if (eContainer instanceof XtendFile) {
            annotationTypeModifierValidator.checkModifiers(annotation, "annotation type " + annotation.getName());
        } else {
            nestedAnnotationTypeModifierValidator.checkModifiers(annotation,
                    "annotation type " + annotation.getName());
        }
    }

    @Check
    protected void checkModifiers(XtendField field) {
        XtendTypeDeclaration declaringType = field.getDeclaringType();
        if (declaringType instanceof XtendClass || declaringType instanceof AnonymousClass) {
            if (field.isFinal() && field.isVolatile()) {
                error("The field " + field.getName() + " can be either final or volatile, not both.",
                        XTEND_FIELD__NAME, -1, INVALID_MODIFIER);
            }
            fieldModifierValidator.checkModifiers(field, "field " + field.getName());
        } else if (declaringType instanceof XtendInterface || declaringType instanceof XtendAnnotationType)
            fieldInInterfaceModifierValidator.checkModifiers(field, "field " + field.getName());
    }

    @Check
    protected void checkModifiers(XtendConstructor constructor) {
        if (!(constructor.getDeclaringType() instanceof XtendClass)) {
            error("Contructors are only permitted within classes", null, CONSTRUCTOR_NOT_PERMITTED);
        } else {
            String typeName = ((XtendTypeDeclaration) constructor.eContainer()).getName();
            constructorModifierValidator.checkModifiers(constructor, "type " + typeName);
        }
    }

    @Check
    protected void checkModifiers(XtendFunction method) {
        XtendTypeDeclaration declaringType = method.getDeclaringType();
        if (declaringType instanceof XtendClass || declaringType instanceof AnonymousClass) {
            methodModifierValidator.checkModifiers(method, "method " + method.getName());
            int abstractIndex = method.getModifiers().indexOf("abstract");
            int nativeIndex = method.getModifiers().indexOf("native");
            if (method.getExpression() != null) {
                if (abstractIndex != -1) {
                    error("Method " + method.getName() + " with a body cannot be abstract", XTEND_MEMBER__MODIFIERS,
                            abstractIndex, INVALID_MODIFIER);
                } else if (method.isNative())
                    error("Native methods do not specify a body", XTEND_FUNCTION__NAME, -1, INVALID_MODIFIER);
            } else if (nativeIndex == -1) {
                int finalIndex = method.getModifiers().indexOf("final");
                if (finalIndex != -1)
                    error("Abstract method " + method.getName() + " cannot be final", XTEND_MEMBER__MODIFIERS,
                            finalIndex, INVALID_MODIFIER);
                int privateIndex = method.getModifiers().indexOf("private");
                if (privateIndex != -1)
                    error("Abstract method " + method.getName() + " cannot be private", XTEND_MEMBER__MODIFIERS,
                            privateIndex, INVALID_MODIFIER);
                int staticIndex = method.getModifiers().indexOf("static");
                if (staticIndex != -1)
                    error("Abstract method " + method.getName() + " cannot be static", XTEND_MEMBER__MODIFIERS,
                            staticIndex, INVALID_MODIFIER);
            }
        } else if (declaringType instanceof XtendInterface) {
            // The validator for interface methods is created lazily when the generator configuration is loaded
            GeneratorConfig config = getGeneratorConfig(method);
            methodInInterfaceModifierValidator.checkModifiers(method, "method " + method.getName());
            int abstractIndex = method.getModifiers().indexOf("abstract");
            if (config.getJavaSourceVersion().isAtLeast(JAVA8) && method.getExpression() != null
                    && abstractIndex != -1) {
                error("Method " + method.getName() + " with a body cannot be abstract", XTEND_MEMBER__MODIFIERS,
                        abstractIndex, INVALID_MODIFIER);
            }
        }
    }

    @Check
    protected void checkInferedApi(XtendFunction method) {
        if (isIgnored(API_TYPE_INFERENCE))
            return;
        if (isApi(method) && method.getReturnType() == null && !method.isOverride()) {
            addIssue("API method needs explicit return type", method, XTEND_FUNCTION__NAME, API_TYPE_INFERENCE);
        }
    }

    @Check
    protected void checkInferedApi(XtendField field) {
        if (isApi(field) && field.getType() == null && field.getInitialValue() != null) {
            addIssue("API field needs explicit type", field, XTEND_FIELD__NAME, API_TYPE_INFERENCE);
        }
    }

    protected boolean isApi(XtendMember member) {
        if (!isApi(member.getDeclaringType()))
            return false;
        return member.getVisibility() == JvmVisibility.PUBLIC
                || member.getVisibility() == JvmVisibility.PROTECTED && !member.getDeclaringType().isFinal();
    }

    protected boolean isApi(XtendTypeDeclaration type) {
        if (type.isAnonymous())
            return false;
        boolean api = type.getVisibility() == JvmVisibility.PUBLIC;
        if (type.getDeclaringType() != null) {
            api = api || (type.getVisibility() == JvmVisibility.PROTECTED && !type.getDeclaringType().isFinal());
            api = api && isApi(type.getDeclaringType());
        }
        return api;
    }

    @Check
    protected void checkImplicitReturn(final XtendFunction method) {
        if (isIgnored(IMPLICIT_RETURN))
            return;
        JvmOperation jvmOperation = associations.getDirectlyInferredOperation(method);
        IResolvedTypes types = batchTypeResolver.resolveTypes(method);
        if (jvmOperation != null && types.getActualType(jvmOperation).isPrimitiveVoid())
            return;
        implicitReturnFinder.findImplicitReturns(method.getExpression(), new Acceptor() {
            @Override
            public void accept(XExpression implicitReturn) {
                if (method.getExpression() == implicitReturn)
                    return;
                addIssue("Implicit return", implicitReturn, IMPLICIT_RETURN);
            }
        });
    }

    @Override
    protected boolean isLocalClassSemantics(EObject object) {
        return super.isLocalClassSemantics(object)
                || (object instanceof XtendMember && !(object instanceof AnonymousClass));
    }

    @Override
    public void checkDeprecated(XImportDeclaration decl) {
        XtendFile file = EcoreUtil2.getContainerOfType(decl, XtendFile.class);
        if (file != null) {
            for (XtendTypeDeclaration t : file.getXtendTypes()) {

                for (EObject e : jvmModelAssociations.getJvmElements(t)) {
                    if (e instanceof JvmAnnotationTarget) {
                        if (DeprecationUtil.isDeprecated((JvmAnnotationTarget) e)) {
                            return;
                        }
                    }
                }

                if (hasAnnotation(t, Deprecated.class)) {
                    return;
                }
            }
        }
        super.checkDeprecated(decl);
    }

    // makes it possible to create issues with configurable severity from delegates e.g. ModifierValidator
    @Override
    protected void addIssue(String message, EObject source, EStructuralFeature feature, int index, String issueCode,
            String... issueData) {
        super.addIssue(message, source, feature, index, issueCode, issueData);
    }

}