org.eclipse.viatra.query.patternlanguage.emf.validation.EMFPatternLanguageValidator.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.viatra.query.patternlanguage.emf.validation.EMFPatternLanguageValidator.java

Source

/*******************************************************************************
 * Copyright (c) 2010-2012, Zoltan Ujhelyi, Istvan Rath and Daniel Varro
 * 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
 *
 * Contributors:
 *   Mark Czotter, Zoltan Ujhelyi - initial API and implementation
 *   Andras Okros - new validators added
 *******************************************************************************/
package org.eclipse.viatra.query.patternlanguage.emf.validation;

import static org.eclipse.xtext.xbase.validation.IssueCodes.IMPORT_UNUSED;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.lang.model.SourceVersion;

import org.apache.log4j.Logger;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.impl.BasicEObjectImpl;
import org.eclipse.viatra.query.patternlanguage.emf.vql.PatternLanguagePackage;
import org.eclipse.viatra.query.patternlanguage.emf.vql.EnumValue;
import org.eclipse.viatra.query.patternlanguage.emf.vql.PackageImport;
import org.eclipse.viatra.query.patternlanguage.emf.vql.PatternImport;
import org.eclipse.viatra.query.patternlanguage.emf.vql.PatternModel;
import org.eclipse.viatra.query.patternlanguage.emf.vql.ReferenceType;
import org.eclipse.viatra.query.patternlanguage.emf.vql.VQLImportSection;
import org.eclipse.viatra.query.patternlanguage.emf.helper.PatternLanguageHelper;
import org.eclipse.viatra.query.patternlanguage.emf.jvmmodel.EMFPatternLanguageJvmModelInferrerUtil;
import org.eclipse.viatra.query.patternlanguage.emf.types.BottomTypeKey;
import org.eclipse.viatra.query.patternlanguage.emf.types.EMFTypeSystem;
import org.eclipse.viatra.query.patternlanguage.emf.types.ITypeInferrer;
import org.eclipse.viatra.query.patternlanguage.emf.vql.CheckConstraint;
import org.eclipse.viatra.query.patternlanguage.emf.vql.CompareConstraint;
import org.eclipse.viatra.query.patternlanguage.emf.vql.CompareFeature;
import org.eclipse.viatra.query.patternlanguage.emf.vql.ComputationValue;
import org.eclipse.viatra.query.patternlanguage.emf.vql.Constraint;
import org.eclipse.viatra.query.patternlanguage.emf.vql.FunctionEvaluationValue;
import org.eclipse.viatra.query.patternlanguage.emf.vql.LiteralValueReference;
import org.eclipse.viatra.query.patternlanguage.emf.vql.ParameterRef;
import org.eclipse.viatra.query.patternlanguage.emf.vql.PathExpressionConstraint;
import org.eclipse.viatra.query.patternlanguage.emf.vql.Pattern;
import org.eclipse.viatra.query.patternlanguage.emf.vql.PatternBody;
import org.eclipse.viatra.query.patternlanguage.emf.vql.PatternCall;
import org.eclipse.viatra.query.patternlanguage.emf.vql.PatternCompositionConstraint;
import org.eclipse.viatra.query.patternlanguage.emf.vql.ValueReference;
import org.eclipse.viatra.query.patternlanguage.emf.vql.Variable;
import org.eclipse.viatra.query.patternlanguage.emf.vql.VariableReference;
import org.eclipse.viatra.query.runtime.base.api.BaseIndexOptions;
import org.eclipse.viatra.query.runtime.base.comprehension.EMFModelComprehension;
import org.eclipse.viatra.query.runtime.emf.types.EClassTransitiveInstancesKey;
import org.eclipse.viatra.query.runtime.emf.types.EDataTypeInSlotsKey;
import org.eclipse.viatra.query.runtime.emf.types.EStructuralFeatureInstancesKey;
import org.eclipse.viatra.query.runtime.matchers.algorithms.UnionFind;
import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
import org.eclipse.viatra.query.runtime.matchers.context.common.JavaTransitiveInstancesKey;
import org.eclipse.viatra.query.runtime.matchers.context.surrogate.SurrogateQueryRegistry;
import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.util.Strings;
import org.eclipse.xtext.validation.AbstractDeclarativeValidator;
import org.eclipse.xtext.validation.Check;
import org.eclipse.xtext.validation.CheckType;
import org.eclipse.xtext.validation.ComposedChecks;
import org.eclipse.xtext.xbase.XExpression;
import org.eclipse.xtext.xbase.jvmmodel.IJvmModelAssociations;

import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.UnmodifiableIterator;
import com.google.inject.Inject;

/**
 * Validators for EMFPattern Language:
 * <ul>
 * <li>Duplicate import of EPackages</li>
 * <li>Enum types</li>
 * <li>Unused variables</li>
 * <li>Type checking for parameters and body variables</li>
 * <li>Type checking for literal and computational values in pattern calls, path expressions and compare constraints
 * <li>Pattern body searching for isolated constraints (cartesian products)</li>
 * <li>Non-EDataTypes in check expression</li>
 * </ul>
 * 
 * @noreference
 */
@ComposedChecks(validators = { PatternLanguageValidator.class, VariableUsageCounter.class,
        ClasspathValidator.class })
public class EMFPatternLanguageValidator extends AbstractEMFPatternLanguageValidator implements IIssueCallback {

    private String calculateIssueData(IInputKey input) {
        if (input instanceof EClassTransitiveInstancesKey) {
            return ((EClassTransitiveInstancesKey) input).getEmfKey().getName();
        } else if (input instanceof EDataTypeInSlotsKey) {
            EDataType datatype = ((EDataTypeInSlotsKey) input).getEmfKey();
            if (datatype instanceof EEnum) {
                return datatype.getName();
            } else {
                // In case of non-enum datatypes use corresponding Java type instead
                return IssueCodes.JAVA_TYPE_PREFIX + typeSystem.getJavaClassName((EDataTypeInSlotsKey) input);
            }
        } else if (input instanceof JavaTransitiveInstancesKey) {
            return IssueCodes.JAVA_TYPE_PREFIX + ((JavaTransitiveInstancesKey) input).getWrappedKey();
        }
        return null;
    }

    private static final class SamePackageUri implements Predicate<PackageImport> {
        private final String nsUri;

        private SamePackageUri(String nsUri) {
            this.nsUri = nsUri;
        }

        @Override
        public boolean apply(PackageImport importDecl) {
            return importDecl != null && nsUri.equals(importDecl.getEPackage().getNsURI());
        }
    }

    private static final String UNUSED_PRIVATE_PATTERN_MESSAGE = "The pattern '%s' is never used locally.";

    @Inject
    private ITypeInferrer typeInferrer;

    @Inject
    private EMFTypeSystem typeSystem;

    @Inject
    private IJvmModelAssociations associations;

    @Inject
    private Logger logger;

    @Inject
    private EMFPatternLanguageJvmModelInferrerUtil inferrerUtil;

    private static class CustomMethodWrapper extends MethodWrapper {

        private Logger logger;

        protected CustomMethodWrapper(AbstractDeclarativeValidator instance, Method m, Logger logger) {
            super(instance, m);
            this.logger = logger;
        }

        @Override
        protected void handleInvocationTargetException(Throwable targetException, State state) {
            // superclass ignores NPEs, instead we should at least log them
            if (targetException instanceof NullPointerException) {
                logger.warn("Unexpected validation error", targetException);
            }
            super.handleInvocationTargetException(targetException, state);
        }

    }

    @Override
    protected MethodWrapper createMethodWrapper(AbstractDeclarativeValidator instanceToUse, Method method) {
        return new CustomMethodWrapper(instanceToUse, method, logger);
    }

    @Override
    protected List<EPackage> getEPackages() {
        // PatternLanguagePackage must be added to the defaults, otherwise the core language validators not used in the
        // validation process
        List<EPackage> result = super.getEPackages();
        result.add(PatternLanguagePackage.eINSTANCE);
        return result;
    }

    @Check
    public void checkDuplicatePackageImports(PatternModel patternModel) {
        List<PackageImport> importPackages = PatternLanguageHelper.getAllPackageImports(patternModel);
        for (int i = 0; i < importPackages.size(); ++i) {
            EPackage leftPackage = importPackages.get(i).getEPackage();
            for (int j = i + 1; j < importPackages.size(); ++j) {
                EPackage rightPackage = importPackages.get(j).getEPackage();
                if (leftPackage.equals(rightPackage)) {
                    warning("Duplicate import of " + leftPackage.getNsURI(),
                            PatternLanguagePackage.Literals.PATTERN_MODEL__IMPORT_PACKAGES, i,
                            IssueCodes.DUPLICATE_IMPORT);
                    warning("Duplicate import of " + rightPackage.getNsURI(),
                            PatternLanguagePackage.Literals.PATTERN_MODEL__IMPORT_PACKAGES, j,
                            IssueCodes.DUPLICATE_IMPORT);
                }
            }
        }
    }

    @Check
    public void checkParametersNamed(Pattern pattern) {
        for (Variable var : pattern.getParameters()) {
            if (var.getName() != null && var.getName().startsWith("_")) {
                error("Parameter name must not start with _", var, PatternLanguagePackage.Literals.VARIABLE__NAME,
                        IssueCodes.SINGLEUSE_PARAMETER);
            }
        }
    }

    @Check
    public void checkEnumValues(EnumValue value) {
        if (value.eContainer() instanceof PathExpressionConstraint) {
            // If container is PathExpression check for enum type assignability
            EEnum enumType = value.getEnumeration();
            if (enumType == null && value.getLiteral() != null) {
                enumType = value.getLiteral().getEEnum();
            }
            PathExpressionConstraint expression = (PathExpressionConstraint) value.eContainer();
            Optional<EEnum> expectedTypeOpt = PatternLanguageHelper.getPathExpressionEMFTailType(expression)
                    .filter(EEnum.class::isInstance).map(EEnum.class::cast);
            if (expectedTypeOpt.isPresent()) {
                EEnum expectedType = expectedTypeOpt.get();
                if (enumType != null && !expectedType.equals(enumType)) {
                    error(String.format("Inconsistent enumeration types: found %s but expected %s",
                            enumType.getName(), expectedType.getName()), value,
                            PatternLanguagePackage.Literals.ENUM_VALUE__ENUMERATION,
                            IssueCodes.INVALID_ENUM_LITERAL);
                }
            } else {
                String name = (enumType == null) ? "<UNKNOWN>" : enumType.getName();
                error(String.format("Invalid enumeration constant %s", name), value,
                        PatternLanguagePackage.Literals.ENUM_VALUE__ENUMERATION, IssueCodes.INVALID_ENUM_LITERAL);
            }
        }
    }

    @Check
    public void checkVariableType(Variable variable) {
        if (PatternLanguageHelper.isParameter(variable)) {
            checkParameterTypes(variable);
        } else {
            checkPatternVariablesType(variable);
        }
    }

    /**
     * A variable's type can come from different sources: parameter's type definition, type definitions in the pattern
     * bodies or calculated from path expression constraints or find calls. In these situations one variable might have
     * conflicting type definitions. In conflicting situations if a variable's multiple types have a common subtype
     * (which would ensure a pattern match runtime) and has a type defined as a parameter, than this type will be
     * selected. In other cases we don't select a random type from the possibilities, the validator returns with an
     * error. Note, if the multiple type definitions are related in subtype-supertype relations than the most specific
     * is selected naturally (this is not even a warning).
     * 
     * @param pattern
     */
    private void checkPatternVariablesType(Variable variable) {
        Set<IInputKey> allPossibleTypes = typeInferrer.getAllPossibleTypes(variable);
        Set<EClassifier> possibleClassifiers = allPossibleTypes.stream().map(input -> {
            if (input instanceof EClassTransitiveInstancesKey) {
                return ((EClassTransitiveInstancesKey) input).getEmfKey();
            } else if (input instanceof EDataTypeInSlotsKey) {
                return ((EDataTypeInSlotsKey) input).getEmfKey();
            }
            return null;
        }).filter(Objects::nonNull).collect(Collectors.toSet());
        // We only need to give warnings/errors if there are multiple possible types
        if (allPossibleTypes.size() <= 1 || allPossibleTypes.contains(BottomTypeKey.INSTANCE)) {
            return;
        }

        Set<String> typeNameSet = Sets.newHashSet(Iterables.transform(allPossibleTypes, typeSystem::typeString));

        Set<String> classifierNamesSet = new HashSet<>();
        Set<String> classifierPackagesSet = new HashSet<>();
        for (EClassifier classifier : possibleClassifiers) {
            classifierNamesSet.add(classifier.getName());
            if (classifier.getEPackage() != null) {
                classifierPackagesSet.add(classifier.getEPackage().getName());
            }
        }

        // If the String sets contains only 1 elements than it is an error
        // There is some element which is defined multiple types within the ecores
        if (possibleClassifiers.size() > 1 && classifierNamesSet.size() == 1 && classifierPackagesSet.size() <= 1) {
            StringBuilder sb = new StringBuilder();
            sb.append("Variable ");
            sb.append(variable.getName());
            sb.append(" has a type ");
            sb.append(classifierNamesSet.iterator().next());
            sb.append(" which has multiple definitions: ");
            for (EClassifier cls : possibleClassifiers) {
                sb.append(" '");
                if (cls.eIsProxy()) {
                    sb.append(((BasicEObjectImpl) cls).eProxyURI());
                } else {
                    sb.append(cls.eResource().getURI());
                }
                sb.append("' -- ");
            }
            // findAny().get() is OK, as at least one reference must exist here
            error(sb.toString(), PatternLanguageHelper.getReferences(variable).findAny().get(), null,
                    IssueCodes.VARIABLE_TYPE_MULTIPLE_DECLARATION);
        }
        final IInputKey declaredType = typeInferrer.getDeclaredType(variable);
        final PatternModel patternModel = EcoreUtil2.getContainerOfType(variable, PatternModel.class);
        if (declaredType == null) {
            if (!typeSystem.hasCommonSubtype(allPossibleTypes,
                    PatternLanguageHelper.getEPackageImportsIterable(patternModel))) {
                if (variable instanceof ParameterRef) {
                    error("Ambiguous variable type definitions: " + typeNameSet
                            + ", type cannot be selected. Please specify the one to be used as the parameter type"
                            + " by adding it to the parameter definition.",
                            ((ParameterRef) variable).getReferredParam(), null,
                            IssueCodes.VARIABLE_TYPE_INVALID_ERROR);
                } else {
                    error("Inconsistent variable type definitions: " + typeNameSet + ", type cannot be selected.",
                            PatternLanguageHelper.getReferences(variable).findAny().get(), null,
                            IssueCodes.VARIABLE_TYPE_INVALID_ERROR);
                }
            }
        } else {
            // Check whether all types conforms to declared type
            List<String> inconsistentTypes = allPossibleTypes.stream()
                    .filter(input -> !typeSystem.isConformant(declaredType, input))
                    .filter(input -> !typeSystem.hasCommonSubtype(ImmutableSet.of(declaredType, input),
                            PatternLanguageHelper.getEPackageImportsIterable(patternModel)))
                    .map(typeSystem::typeString).collect(Collectors.toList());
            if (!inconsistentTypes.isEmpty()) {
                error("Variable types [" + Joiner.on(", ").join(inconsistentTypes)
                        + "] do not conform to declared type " + typeSystem.typeString(declaredType),
                        PatternLanguageHelper.getReferences(variable).findAny().get(), null,
                        IssueCodes.VARIABLE_TYPE_INVALID_ERROR);
            }

        }
    }

    private void checkParameterTypes(final Variable variable) {
        Set<Variable> parameterReferences = PatternLanguageHelper.getLocalReferencesOfParameter(variable).stream()
                .filter(Objects::nonNull).collect(Collectors.toSet());

        final IInputKey inferredType = typeInferrer.getType(variable);
        if (variable.getType() == null) {
            // Missing type validation
            Set<IInputKey> possibleTypes = Sets.newHashSet();

            for (Variable bodyVar : parameterReferences) {
                possibleTypes.addAll(typeInferrer.getAllPossibleTypes(bodyVar));
            }
            reportMissingParameterTypeDeclaration(variable, possibleTypes, typeInferrer.getInferredType(variable));

            if (possibleTypes.isEmpty()) {
                return;
            } else if (possibleTypes.size() > 1
                    && Iterables.all(possibleTypes, EClassTransitiveInstancesKey.class::isInstance)) {
                Set<EClass> eClasses = typeSystem.getCompatibleSupertypes(possibleTypes).stream()
                        .filter(EClassTransitiveInstancesKey.class::isInstance)
                        .map(EClassTransitiveInstancesKey.class::cast).map(EClassTransitiveInstancesKey::getEmfKey)
                        .filter(input -> input != null && !input.eIsProxy()).collect(Collectors.toSet());

                final EClass erroneous = EcoreFactory.eINSTANCE.createEClass();
                Optional<EClass> reduced = eClasses.stream().filter(Objects::nonNull).filter(obj -> !obj.eIsProxy())
                        .reduce((t1, t2) -> {
                            if (t1 == EcorePackage.Literals.EOBJECT) {
                                return t2;
                            } else {
                                final EClass compatibleType = (EClass) EcoreUtil2.getCompatibleType(t1, t2, null);
                                return compatibleType == null ? erroneous : compatibleType;
                            }
                        });
                if (Objects.equals(reduced.orElse(erroneous), erroneous)) {
                    String[] issueData = Stream.concat(eClasses.stream().map(EClass::getName), Stream.of("EObject"))
                            .toArray(String[]::new);
                    error("Variable type cannot be calculated unambiguously, the types ["
                            + Joiner.on(", ").join(Iterables.transform(possibleTypes, typeSystem::typeString))
                            + "] have no _unique_ common supertype. The list of possible supertypes found are ["
                            + Joiner.on(", ").join(issueData) + "], specify one as the intended supertype.",
                            variable, null, IssueCodes.PARAMETER_TYPE_AMBIGUOUS, issueData);
                }
            }
        } else {
            // Check for more specific type inferrable for bodies
            Set<IInputKey> referenceTypes = parameterReferences.stream().map(typeInferrer::getInferredType)
                    .filter(Objects::nonNull).collect(Collectors.toSet());
            boolean allTypesMoreSpecific = referenceTypes.stream()
                    .allMatch(aggregatedType -> !Objects.equals(inferredType, aggregatedType)
                            && Objects.equals(inferredType.getClass(), aggregatedType.getClass())
                            && typeSystem.isConformant(inferredType, aggregatedType));
            Iterator<IInputKey> it = referenceTypes.iterator();
            if (it.hasNext() && allTypesMoreSpecific) {
                Set<IInputKey> aggregatedTypes = typeSystem.minimizeTypeInformation(Sets.newHashSet(referenceTypes),
                        true);
                if (aggregatedTypes.size() == 1 && inferredType != null) {
                    IInputKey aggregatedType = aggregatedTypes.iterator().next();
                    if (!Objects.equals(inferredType, aggregatedType)
                            && Objects.equals(inferredType.getClass(), aggregatedType.getClass())
                            && typeSystem.isConformant(inferredType, aggregatedType)) {
                        warning("Declared type " + typeSystem.typeString(inferredType)
                                + " is less specific then the type " + typeSystem.typeString(aggregatedType)
                                + " inferred from bodies", variable, null, IssueCodes.PARAMETER_TYPE_INVALID);
                    }
                }
            }
        }
    }

    private void reportMissingParameterTypeDeclaration(Variable parameter, Set<IInputKey> possibleTypes,
            IInputKey inferredType) {
        if (possibleTypes.isEmpty()) {
            return;
        } else if (possibleTypes.size() == 1 && !(possibleTypes.iterator().next() instanceof BottomTypeKey)) {
            String[] issueData = new String[] { calculateIssueData(inferredType) };
            warning("Type not defined for variable " + parameter.getName() + ", inferred type "
                    + typeSystem.typeString(inferredType) + " is used instead.",
                    PatternLanguagePackage.Literals.VARIABLE__NAME, IssueCodes.MISSING_PARAMETER_TYPE, issueData);
        } else {
            Set<IInputKey> orderedTypes = ImmutableSortedSet.<IInputKey>orderedBy((o1, o2) -> {
                if (o1 instanceof EClassTransitiveInstancesKey && !(o2 instanceof EClassTransitiveInstancesKey)) {
                    return +1;
                } else if (o2 instanceof EClassTransitiveInstancesKey
                        && !(o1 instanceof EClassTransitiveInstancesKey)) {
                    return -1;
                } else if (o1 instanceof EDataTypeInSlotsKey && !(o2 instanceof EDataTypeInSlotsKey)) {
                    return +1;
                } else if (o2 instanceof EDataTypeInSlotsKey && !(o1 instanceof EDataTypeInSlotsKey)) {
                    return +1;
                } else if (typeSystem.isConformant(o1, o2)) { //Common type group
                    return +1;
                } else if (typeSystem.isConformant(o2, o1)) {
                    return -1;
                }
                return 0;
            }).addAll(possibleTypes).build();
            Set<String> superClasses = (Iterables.any(possibleTypes,
                    EClassTransitiveInstancesKey.class::isInstance)) ? ImmutableSet.of("EObject")
                            : ImmutableSet.of(IssueCodes.JAVA_TYPE_PREFIX + "java.lang.Object");
            Iterable<String> typeNames = Iterables.concat(Iterables
                    .filter(Iterables.transform(orderedTypes, this::calculateIssueData), Predicates.notNull()),
                    superClasses);
            String[] issueData = Iterables.toArray(typeNames, String.class);
            if (issueData.length > 0) {
                warning("Type not defined for variable " + parameter.getName() + ", inferred type "
                        + typeSystem.typeString(inferredType) + " is used instead.",
                        PatternLanguagePackage.Literals.VARIABLE__NAME, IssueCodes.MISSING_PARAMETER_TYPE,
                        issueData);
            }
        }
    }

    /**
     * A validator for cartesian products (isolated constraints) in pattern bodies. There are two types of warnings:
     * strict and soft. Strict warning means that there are constraints in the body which has no connection at all, in
     * soft cases they connected at least with a count find. The validator's result always just a warning, however a
     * strict warning usually a modeling design flaw which should be corrected.
     * 
     * @param patternBody
     */
    @Check(CheckType.NORMAL)
    public void checkForCartesianProduct(PatternBody patternBody) {
        List<Variable> variables = Lists.newArrayList(patternBody.getVariables());
        List<Variable> unnamedRunningVariables = PatternLanguageHelper.getUnnamedRunningVariables(patternBody);
        variables.removeAll(unnamedRunningVariables);
        UnionFind<Variable> justPositiveUnionFindForVariables = new UnionFind<>(variables);
        UnionFind<Variable> generalUnionFindForVariables = new UnionFind<>(variables);
        boolean isSecondRunNeeded = false;

        // First run
        // Just put together the real positive connections, and all of the general connections first
        for (Constraint constraint : patternBody.getConstraints()) {
            Set<Variable> positiveVariables = new HashSet<>();
            Set<Variable> generalVariables = new HashSet<>();
            if (constraint instanceof CompareConstraint) {
                // Equality and inequality (==, !=)
                CompareConstraint compareConstraint = (CompareConstraint) constraint;
                ValueReference leftValueReference = compareConstraint.getLeftOperand();
                ValueReference rightValueReference = compareConstraint.getRightOperand();
                Set<Variable> leftVariables = PatternLanguageHelper
                        .getVariablesFromValueReference(leftValueReference);
                Set<Variable> rightVariables = PatternLanguageHelper
                        .getVariablesFromValueReference(rightValueReference);
                if (CompareFeature.EQUALITY.equals(compareConstraint.getFeature())) {
                    // Equality ==
                    if (!isValueReferenceComputed(leftValueReference)
                            && !isValueReferenceComputed(rightValueReference)) {
                        positiveVariables.addAll(leftVariables);
                        positiveVariables.addAll(rightVariables);
                        generalVariables.addAll(leftVariables);
                        generalVariables.addAll(rightVariables);
                    } else {
                        isSecondRunNeeded = true;
                        generalVariables.addAll(leftVariables);
                        generalVariables.addAll(rightVariables);
                    }
                } else if (CompareFeature.INEQUALITY.equals(compareConstraint.getFeature())) {
                    // Inequality !=
                    generalVariables.addAll(leftVariables);
                    generalVariables.addAll(rightVariables);
                }
            } else if (constraint instanceof PatternCompositionConstraint) {
                // Find and neg-find constructs
                PatternCompositionConstraint patternCompositionConstraint = (PatternCompositionConstraint) constraint;
                if (!patternCompositionConstraint.isNegative()) {
                    // Positive composition (find)
                    for (ValueReference valueReference : PatternLanguageHelper
                            .getCallParameters(patternCompositionConstraint.getCall())) {
                        if (!isValueReferenceComputed(valueReference)) {
                            positiveVariables
                                    .addAll(PatternLanguageHelper.getVariablesFromValueReference(valueReference));
                            generalVariables
                                    .addAll(PatternLanguageHelper.getVariablesFromValueReference(valueReference));
                        } else {
                            isSecondRunNeeded = true;
                            generalVariables
                                    .addAll(PatternLanguageHelper.getVariablesFromValueReference(valueReference));
                        }
                    }
                } else {
                    // Negative composition (neg find)
                    for (ValueReference valueReference : PatternLanguageHelper
                            .getCallParameters(patternCompositionConstraint.getCall())) {
                        generalVariables
                                .addAll(PatternLanguageHelper.getVariablesFromValueReference(valueReference));
                    }
                }
            } else if (constraint instanceof PathExpressionConstraint) {
                // Normal attribute-reference constraint
                PathExpressionConstraint pathExpressionConstraint = (PathExpressionConstraint) constraint;
                ValueReference valueReference = pathExpressionConstraint.getDst();
                Variable pathExpressionHeadSourceVariable = null;
                if (pathExpressionConstraint.getSrc() != null) {
                    pathExpressionHeadSourceVariable = pathExpressionConstraint.getSrc().getVariable();
                }
                if (!isValueReferenceComputed(valueReference)) {
                    positiveVariables.addAll(PatternLanguageHelper.getVariablesFromValueReference(valueReference));
                    positiveVariables.add(pathExpressionHeadSourceVariable);
                    generalVariables.addAll(PatternLanguageHelper.getVariablesFromValueReference(valueReference));
                    generalVariables.add(pathExpressionHeadSourceVariable);
                } else {
                    isSecondRunNeeded = true;
                    generalVariables.addAll(PatternLanguageHelper.getVariablesFromValueReference(valueReference));
                    generalVariables.add(pathExpressionHeadSourceVariable);
                }
            } else if (constraint instanceof CheckConstraint) {
                // Variables used together in check expression, always negative
                CheckConstraint checkConstraint = (CheckConstraint) constraint;
                generalVariables.addAll(PatternLanguageHelper
                        .getReferencedPatternVariablesOfXExpression(checkConstraint.getExpression(), associations));
            }
            justPositiveUnionFindForVariables.unite(positiveVariables);
            generalUnionFindForVariables.unite(generalVariables);
        }

        // Second run
        // If variables in a computation formula (e.g.: count find Pattern(X,Y)) are in the same union in the positive
        // case then they are considered to be in a positive relation with the respective target as well
        // M == count find Pattern(X,Y), so M with X and Y is positive if X and Y is positive
        // If the aggregated contains unnamed/running vars it should be omitted during the positive relation checking
        if (isSecondRunNeeded) {
            for (Constraint constraint : patternBody.getConstraints()) {
                Set<Variable> positiveVariables = new HashSet<>();
                if (constraint instanceof CompareConstraint) {
                    CompareConstraint compareConstraint = (CompareConstraint) constraint;
                    if (CompareFeature.EQUALITY.equals(compareConstraint.getFeature())) {
                        // Equality (==), with aggregates in it
                        ValueReference leftValueReference = compareConstraint.getLeftOperand();
                        ValueReference rightValueReference = compareConstraint.getRightOperand();
                        if (isValueReferenceComputed(leftValueReference)
                                || isValueReferenceComputed(rightValueReference)) {
                            addPositiveVariablesFromValueReference(unnamedRunningVariables,
                                    justPositiveUnionFindForVariables, positiveVariables, leftValueReference);
                            addPositiveVariablesFromValueReference(unnamedRunningVariables,
                                    justPositiveUnionFindForVariables, positiveVariables, rightValueReference);
                        }
                    }
                } else if (constraint instanceof PatternCompositionConstraint) {
                    PatternCompositionConstraint patternCompositionConstraint = (PatternCompositionConstraint) constraint;
                    if (!patternCompositionConstraint.isNegative()) {
                        // Positive composition (find), with aggregates in it
                        for (ValueReference valueReference : PatternLanguageHelper
                                .getCallParameters(patternCompositionConstraint.getCall())) {
                            addPositiveVariablesFromValueReference(unnamedRunningVariables,
                                    justPositiveUnionFindForVariables, positiveVariables, valueReference);
                        }
                    }
                } else if (constraint instanceof PathExpressionConstraint) {
                    // Normal attribute-reference constraint, with aggregates in it
                    PathExpressionConstraint pathExpressionConstraint = (PathExpressionConstraint) constraint;
                    Variable pathExpressionHeadSourceVariable = null;
                    if (pathExpressionConstraint.getSrc() != null) {
                        pathExpressionHeadSourceVariable = pathExpressionConstraint.getSrc().getVariable();
                    }
                    positiveVariables.add(pathExpressionHeadSourceVariable);
                    ValueReference valueReference = pathExpressionConstraint.getDst();
                    addPositiveVariablesFromValueReference(unnamedRunningVariables,
                            justPositiveUnionFindForVariables, positiveVariables, valueReference);
                }
                justPositiveUnionFindForVariables.unite(positiveVariables);
            }
        }

        // Remove variables which are equated to constants
        for (Constraint constraint : patternBody.getConstraints()) {
            if (constraint instanceof CompareConstraint) {
                // Just equality
                CompareConstraint compareConstraint = (CompareConstraint) constraint;
                if (CompareFeature.EQUALITY.equals(compareConstraint.getFeature())) {
                    ValueReference leftValueReference = compareConstraint.getLeftOperand();
                    ValueReference rightValueReference = compareConstraint.getRightOperand();
                    if (isConstantExpression(patternBody, leftValueReference)
                            && rightValueReference instanceof VariableReference) {
                        VariableReference variableReference = (VariableReference) rightValueReference;
                        Variable variableToRemove = variableReference.getVariable();
                        generalUnionFindForVariables = copyAndRemove(generalUnionFindForVariables,
                                variableToRemove);
                        justPositiveUnionFindForVariables = copyAndRemove(justPositiveUnionFindForVariables,
                                variableToRemove);
                    } else if (leftValueReference instanceof VariableReference
                            && isConstantExpression(patternBody, rightValueReference)) {
                        VariableReference variableReference = (VariableReference) leftValueReference;
                        Variable variableToRemove = variableReference.getVariable();
                        generalUnionFindForVariables = copyAndRemove(generalUnionFindForVariables,
                                variableToRemove);
                        justPositiveUnionFindForVariables = copyAndRemove(justPositiveUnionFindForVariables,
                                variableToRemove);
                    }
                }
            }
        }

        if (generalUnionFindForVariables.getPartitions().size() > 1) {
            // Giving strict warning in this case
            warning("The pattern body contains isolated constraints (\"cartesian products\") that can lead to severe performance and memory footprint issues. The independent partitions are: "
                    + prettyPrintPartitions(generalUnionFindForVariables) + ".", patternBody, null,
                    IssueCodes.CARTESIAN_STRICT_WARNING);
        } else if (justPositiveUnionFindForVariables.getPartitions().size() > 1) {
            // Giving soft warning in this case
            warning("The pattern body contains constraints which are only loosely connected. This may negatively impact performance. The weakly dependent partitions are: "
                    + prettyPrintPartitions(justPositiveUnionFindForVariables), patternBody, null,
                    IssueCodes.CARTESIAN_SOFT_WARNING);
        }
    }

    /**
     * A value reference is constant, if it represents either (1) a literal value, (2) an enum or (3) a eval with no
     * incoming parameters.
     */
    private boolean isConstantExpression(PatternBody body, ValueReference reference) {
        return (reference instanceof LiteralValueReference) || (reference instanceof EnumValue)
                || (reference instanceof FunctionEvaluationValue && PatternLanguageHelper.getUsedVariables(
                        ((FunctionEvaluationValue) reference).getExpression(), body.getVariables()).isEmpty());
    }

    private void addPositiveVariablesFromValueReference(List<Variable> unnamedRunningVariables,
            UnionFind<Variable> justPositiveUnionFindForVariables, Set<Variable> positiveVariables,
            ValueReference valueReference) {
        Set<Variable> leftVariables = PatternLanguageHelper.getVariablesFromValueReference(valueReference);
        leftVariables.removeAll(unnamedRunningVariables);
        if (justPositiveUnionFindForVariables.isSameUnion(leftVariables)) {
            positiveVariables.addAll(leftVariables);
        }
    }

    /**
     * Returns a copy of this with the given value removed. The given value does not have to be a set's root node.
     */
    private static <V> UnionFind<V> copyAndRemove(UnionFind<V> unionFind, V element) {
        UnionFind<V> result = new UnionFind<>();
        for (Set<V> partition : unionFind.getPartitions()) {
            Set<V> filteredPartition = new HashSet<>(partition);
            filteredPartition.remove(element);
            result.makeSet(filteredPartition);
        }
        return result;
    }

    private static String prettyPrintPartitions(UnionFind<Variable> unionFind) {
        StringBuilder result = new StringBuilder();
        for (Set<Variable> partition : unionFind.getPartitions()) {
            result.append("[");
            result.append(partition.stream().map(Variable::getName).collect(Collectors.joining(", ")));
            result.append("]");
        }
        return result.toString();
    }

    private static boolean isValueReferenceComputed(ValueReference valueReference) {
        return valueReference instanceof ComputationValue;
    }

    /**
     * This validator checks if the literal or computational values match the other side's type in a compare constraint
     * (equality/inequality). Both sides can be literal, we will do the check if at least on side is that.
     * 
     * @param compareConstraint
     */
    @Check
    public void checkForWrongLiteralAndComputationValuesInCompareConstraints(CompareConstraint compareConstraint) {
        // Equality and inequality (==, !=)
        ValueReference leftValueReference = compareConstraint.getLeftOperand();
        ValueReference rightValueReference = compareConstraint.getRightOperand();
        if ((leftValueReference instanceof LiteralValueReference || leftValueReference instanceof ComputationValue
                || rightValueReference instanceof LiteralValueReference
                || rightValueReference instanceof ComputationValue)
                && !(leftValueReference instanceof VariableReference)
                && !(rightValueReference instanceof VariableReference)) {
            IInputKey leftClassifier = typeInferrer.getType(leftValueReference);
            IInputKey rightClassifier = typeInferrer.getType(rightValueReference);
            if (!typeSystem.isConformant(leftClassifier, rightClassifier)) {
                final String leftName = leftClassifier == null ? "null" : leftClassifier.getPrettyPrintableName();
                final String rightName = rightClassifier == null ? "null"
                        : rightClassifier.getPrettyPrintableName();
                error("The types of the literal/computational values are different: " + leftName + ", " + rightName
                        + ".", compareConstraint, null, IssueCodes.LITERAL_OR_COMPUTATION_TYPE_MISMATCH_IN_COMPARE);
            }
        }
    }

    /**
     * This validator checks if the literal or computational values match the path expression's type.
     * 
     * @param pathExpressionConstraint
     */
    @Check
    public void checkForWrongLiteralAndComputationValuesInPathExpressionConstraints(
            PathExpressionConstraint pathExpressionConstraint) {
        // Normal attribute-reference constraint
        ValueReference valueReference = pathExpressionConstraint.getDst();
        if (valueReference instanceof LiteralValueReference || valueReference instanceof ComputationValue) {
            IInputKey actualType = typeInferrer.getType(valueReference);
            IInputKey expectedType = PatternLanguageHelper.getPathExpressionTailType(pathExpressionConstraint)
                    .map(typeSystem::extractTypeDescriptor).orElse(BottomTypeKey.INSTANCE);
            if (!typeSystem.isConformant(expectedType, actualType)) {
                String name = expectedType == null ? "<unknown>" : typeSystem.typeString(expectedType);
                error("The type inferred from the path expression (" + name
                        + ") is different from the input literal/computational value ("
                        + typeSystem.typeString(actualType) + ").", pathExpressionConstraint, null,
                        IssueCodes.LITERAL_OR_COMPUTATION_TYPE_MISMATCH_IN_PATH_EXPRESSION);
            }
        }
    }

    /**
     * This validator checks if the literal or computational values match the pattern call's type.
     * 
     * @param patternCall
     */
    @Check
    public void checkForWrongLiteralAndComputationValuesInPatternCalls(PatternCall patternCall) {
        if (patternCall.getPatternRef() == null || patternCall.getPatternRef().eIsProxy()
                || patternCall.getParameters().size() != patternCall.getPatternRef().getParameters().size()) {
            // This kind of error is detected in another place, however it throws an exception during literal checks
            return;
        }
        // Find and neg find (including count find as well)
        for (ValueReference valueReference : patternCall.getParameters()) {
            if (valueReference instanceof LiteralValueReference || valueReference instanceof ComputationValue) {
                Pattern pattern = patternCall.getPatternRef();
                Variable variable = pattern.getParameters()
                        .get(patternCall.getParameters().indexOf(valueReference));
                IInputKey parameterType = typeInferrer.getType(variable);
                IInputKey inputType = typeInferrer.getType(valueReference);
                if (!typeSystem.isConformant(parameterType, inputType)) {
                    final String typeClassifierName = parameterType == null ? "(unknown)"
                            : typeSystem.typeString(parameterType);
                    error("The type inferred from the called pattern (" + typeClassifierName
                            + ") is different from the input literal/computational value ("
                            + typeSystem.typeString(inputType) + ").", patternCall, null,
                            IssueCodes.LITERAL_OR_COMPUTATION_TYPE_MISMATCH_IN_PATTERN_CALL);
                }
            }
        }
    }

    /**
     * This validator looks up all variables in the {@link CheckConstraint} and reports an error if one them is not an
     * {@link EDataType} instance. We do not allow arbitrary EMF elements in, so the checks are less likely to have
     * side-effects.
     */
    @Check
    public void checkForWrongVariablesInXExpressions(CheckConstraint checkConstraint) {
        checkForWrongVariablesInXExpressionsInternal(checkConstraint.getExpression());
    }

    /**
     * This validator looks up all variables in the {@link FunctionEvaluationValue} and reports an error if one them is
     * not an {@link EDataType} instance. We do not allow arbitrary EMF elements in, so the checks are less likely to
     * have side-effects.
     */
    @Check
    public void checkForWrongVariablesInXExpressions(FunctionEvaluationValue eval) {
        checkForWrongVariablesInXExpressionsInternal(eval.getExpression());
    }

    private void checkForWrongVariablesInXExpressionsInternal(final XExpression expression) {
        for (Variable variable : PatternLanguageHelper.getReferencedPatternVariablesOfXExpression(expression,
                associations)) {
            IInputKey classifier = typeInferrer.getType(variable);
            if (classifier instanceof BottomTypeKey) {
                error("Only simple EDataTypes are allowed in check() and eval() expressions. The variable "
                        + variable.getName() + " has an unknown type.", expression.eContainer(), null,
                        IssueCodes.CHECK_CONSTRAINT_SCALAR_VARIABLE_ERROR);
            } else if (classifier != null && !(classifier instanceof EDataTypeInSlotsKey)
                    && !(classifier instanceof JavaTransitiveInstancesKey)) {// null-check needed, otherwise code throws
                                                                                                                                                           // NPE for classifier.getName()
                error("Only simple EDataTypes are allowed in check() and eval() expressions. The variable "
                        + variable.getName() + " has a type of " + classifier.getPrettyPrintableName() + ".",
                        expression.eContainer(), null, IssueCodes.CHECK_CONSTRAINT_SCALAR_VARIABLE_ERROR);
            }
        }
    }

    /**
     * This validator looks up all {@link EStructuralFeature} used in a {@link PathExpressionConstraint} and reports a
     * warning on each that is not representable by VIATRA Query. This is a warning, since we only see well-behaving
     * extensions in the host.
     * 
     * @param pathExpressionConstraint
     */
    @Check
    public void checkForNotWellbehavingDerivedFeatureInPathExpressions(
            PathExpressionConstraint pathExpressionConstraint) {
        EMFModelComprehension comprehension = new EMFModelComprehension(new BaseIndexOptions());
        final List<ReferenceType> edgeTypes = pathExpressionConstraint.getEdgeTypes();
        for (int i = 0; i < edgeTypes.size(); i++) {
            EStructuralFeature feature = edgeTypes.get(i).getRefname();
            if (feature != null && !feature.eIsProxy() && !comprehension.representable(feature)) {
                final EStructuralFeatureInstancesKey featureInputKey = new EStructuralFeatureInstancesKey(feature);
                if (SurrogateQueryRegistry.instance().hasSurrogateQueryFQN(featureInputKey)) {
                    final PQuery surrogateQuery = SurrogateQueryRegistry.instance()
                            .getSurrogateQuery(featureInputKey);
                    String surrogateQueryFQN = surrogateQuery == null ? "(null)"
                            : surrogateQuery.getFullyQualifiedName();
                    info("The derived/volatile feature " + feature.getName() + " of class "
                            + feature.getEContainingClass().getName()
                            + " used in the path expression has a surrogate query " + surrogateQueryFQN
                            + " which will be used by VIATRA Query.", pathExpressionConstraint,
                            PatternLanguagePackage.Literals.PATH_EXPRESSION_CONSTRAINT__EDGE_TYPES, i,
                            IssueCodes.SURROGATE_QUERY_EXISTS);
                } else {
                    warning("The derived/volatile feature " + feature.getName() + " of class "
                            + feature.getEContainingClass().getName()
                            + " used in the path expression is not representable in VIATRA Query."
                            + " For details, consult the documentation on well-behaving features.",
                            pathExpressionConstraint,
                            PatternLanguagePackage.Literals.PATH_EXPRESSION_CONSTRAINT__EDGE_TYPES, i,
                            IssueCodes.FEATURE_NOT_REPRESENTABLE);
                }
            }
        }
    }

    @Check
    public void checkPatternName(Pattern pattern) {
        if (pattern.getName() != null && !SourceVersion.isName(pattern.getName())) {
            JvmType inferredSpecification = inferrerUtil.findInferredSpecification(pattern);
            if (inferredSpecification != null && !inferredSpecification.eIsProxy()
                    && !SourceVersion.isName(inferredSpecification.getQualifiedName())) {
                error(String.format("The pattern name %s is not a valid Java classname", pattern.getName()),
                        PatternLanguagePackage.Literals.PATTERN__NAME, IssueCodes.OTHER_ISSUE);
            }
        }
    }

    @Check
    public void checkReferredPackages(ReferenceType type) {
        if (type.getRefname() == null || type.getRefname().eIsProxy()) {
            // If reference name is not set, do not check package
            return;
        }
        final EClass referredType = type.getRefname().getEContainingClass();
        final EPackage referredPackage = referredType.getEPackage();
        final String nsUri = Strings.emptyIfNull(referredPackage.getNsURI());
        final EObject rootContainer = EcoreUtil2.getRootContainer(type);
        if (rootContainer instanceof PatternModel) {
            PatternModel model = (PatternModel) rootContainer;
            if (model.getImportPackages() != null
                    && !Iterables.any(model.getImportPackages().getPackageImport(), new SamePackageUri(nsUri))) {
                error(String.format("Reference to an EClass %s that is not imported from EPackage %s.",
                        referredType.getName(), nsUri), type,
                        PatternLanguagePackage.Literals.REFERENCE_TYPE__REFNAME, IssueCodes.MISSING_PACKAGE_IMPORT,
                        nsUri);
            }

        }
    }

    @SuppressWarnings("restriction")
    @Check
    public void checkPatternImports(VQLImportSection section) {
        if (!isIgnored(IMPORT_UNUSED)) {
            final Set<Pattern> usedPatterns = Sets.newHashSet();
            final UnmodifiableIterator<PatternCall> it = Iterators.filter(section.eResource().getAllContents(),
                    PatternCall.class);
            while (it.hasNext()) {
                PatternCall call = it.next();
                usedPatterns.add(call.getPatternRef());
            }
            for (PatternImport decl : section.getPatternImport()) {
                if (!usedPatterns.contains(decl.getPattern())) {
                    warning("The import '" + PatternLanguageHelper.getFullyQualifiedName(decl.getPattern())
                            + "' is never used.", decl, null, IMPORT_UNUSED);
                }
            }

        }
    }

    @Check
    public void checkPrivatePatternUsage(Pattern pattern) {
        if (PatternLanguageHelper.isPrivate(pattern) && !isLocallyUsed(pattern, pattern.eContainer())) {
            String message = String.format(UNUSED_PRIVATE_PATTERN_MESSAGE, pattern.getName());
            warning(message, PatternLanguagePackage.Literals.PATTERN__NAME, IssueCodes.UNUSED_PRIVATE_PATTERN);
        }
    }

    @Override
    public void warning(String message, EObject source, EStructuralFeature feature, String code,
            String... issueData) {
        super.warning(message, source, feature, code, issueData);
    }

    @Override
    public void error(String message, EObject source, EStructuralFeature feature, String code,
            String... issueData) {
        super.error(message, source, feature, code, issueData);
    }
}