org.eclipse.incquery.runtime.localsearch.planner.LocalSearchRuntimeBasedStrategy.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.incquery.runtime.localsearch.planner.LocalSearchRuntimeBasedStrategy.java

Source

/*******************************************************************************
 * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, 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:
 *   Marton Bur - initial API and implementation
 *******************************************************************************/
package org.eclipse.incquery.runtime.localsearch.planner;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.incquery.runtime.emf.types.EStructuralFeatureInstancesKey;
import org.eclipse.incquery.runtime.localsearch.matcher.integration.LocalSearchHintKeys;
import org.eclipse.incquery.runtime.localsearch.planner.util.OperationCostComparator;
import org.eclipse.incquery.runtime.matchers.context.IInputKey;
import org.eclipse.incquery.runtime.matchers.context.IQueryMetaContext;
import org.eclipse.incquery.runtime.matchers.context.IQueryRuntimeContext;
import org.eclipse.incquery.runtime.matchers.planning.SubPlan;
import org.eclipse.incquery.runtime.matchers.planning.SubPlanFactory;
import org.eclipse.incquery.runtime.matchers.planning.operations.PApply;
import org.eclipse.incquery.runtime.matchers.planning.operations.PProject;
import org.eclipse.incquery.runtime.matchers.planning.operations.PStart;
import org.eclipse.incquery.runtime.matchers.psystem.PBody;
import org.eclipse.incquery.runtime.matchers.psystem.PConstraint;
import org.eclipse.incquery.runtime.matchers.psystem.PVariable;
import org.eclipse.incquery.runtime.matchers.psystem.basicdeferred.ExportedParameter;
import org.eclipse.incquery.runtime.matchers.psystem.basicdeferred.PatternMatchCounter;
import org.eclipse.incquery.runtime.matchers.psystem.basicenumerables.TypeConstraint;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;

/**
 * This class contains the logic for local search plan calculation based on costs of the operations.
 * Its name refers to the fact that the strategy tries to use as much information as available about
 * the model on which the matching is initiated. When no runtime info is available, it falls back to 
 * the information available from the metamodel durint operation cost calculation.
 * 
 * The implementation is based on the paper "Gergely Varr, Frederik Deckwerth, Martin Wieber, and Andy Schrr: 
 * An algorithm for generating model-sensitive search plans for pattern matching on EMF models" 
 * (DOI: 10.1007/s10270-013-0372-2)
 * 
 * @author Marton Bur
 *
 */
public class LocalSearchRuntimeBasedStrategy {

    private boolean allowInverseNavigation;
    private boolean useIndex;

    public LocalSearchRuntimeBasedStrategy() {
        this(true, true);
    }

    public LocalSearchRuntimeBasedStrategy(final boolean allowInverseNavigation, boolean useIndex) {
        this.allowInverseNavigation = allowInverseNavigation;
        this.useIndex = useIndex;
    }

    /**
     * The implementation of a local search-based algorithm to create a search plan for a flattened (and normalized)
     * PBody
     * @param pBody for which the plan is to be created
     * @param logger that logs the happenings
     * @param initialBoundVariables variables that are known to have already assigned values
     * @param metaContext the metamodel related information
     * @param runtimeContext the instance model related information
     * @param hints the optional hints for the plan creation
     * @return the complete search plan for the given {@link PBody}
     */
    public SubPlan plan(PBody pBody, Logger logger, Set<PVariable> initialBoundVariables,
            IQueryMetaContext metaContext, IQueryRuntimeContext runtimeContext, Map<String, Object> hints) {

        // 1. INITIALIZATION
        // Create a starting plan
        SubPlanFactory subPlanFactory = new SubPlanFactory(pBody);

        // We assume that the adornment (now the bound variables) is previously set
        SubPlan plan = subPlanFactory.createSubPlan(new PStart(initialBoundVariables));
        // Create mask infos
        Set<PConstraint> constraintSet = pBody.getConstraints();
        List<PConstraintInfo> constraintInfos = createPConstraintInfos(constraintSet, runtimeContext);

        // Calculate the characteristic function
        // The characteristic function tells whether a given adornment is backward reachable from the (B)* state, where
        // each variable is bound.
        // The characteristic function is represented as a set of set of variables
        // TODO this calculation is not not implemented yet, thus the contents of the returned set is not considered later
        List<Set<PVariable>> reachableBoundVariableSets = reachabilityAnalysis(pBody, constraintInfos);
        int k = 4;
        Integer rowCountHint = (Integer) hints.get(LocalSearchHintKeys.PLANNER_TABLE_ROW_COUNT);
        if (rowCountHint != null) {
            k = rowCountHint;
        }
        PlanState searchPlan = calculateSearchPlan(pBody, initialBoundVariables, k, reachableBoundVariableSets,
                constraintInfos);

        List<PConstraintInfo> operations = searchPlan.getOperations();
        for (PConstraintInfo pConstraintPlanInfo : operations) {
            PConstraint pConstraint = pConstraintPlanInfo.getConstraint();
            plan = subPlanFactory.createSubPlan(new PApply(pConstraint), plan);
        }
        for (ExportedParameter exportedParameter : pBody.getConstraintsOfType(ExportedParameter.class)) {
            plan = subPlanFactory.createSubPlan(new PApply(exportedParameter), plan);
        }

        return subPlanFactory.createSubPlan(new PProject(pBody.getSymbolicParameterVariables()), plan);
    }

    private PlanState calculateSearchPlan(PBody pBody, Set<PVariable> initialBoundVariables, int k,
            List<Set<PVariable>> reachableBoundVariableSets, List<PConstraintInfo> allMaskInfos) {

        // rename for better understanding
        Set<PVariable> boundVariables = initialBoundVariables;
        Set<PVariable> freeVariables = Sets.difference(pBody.getUniqueVariables(), initialBoundVariables);

        int n = freeVariables.size();

        List<List<PlanState>> stateTable = initializeStateTable(k, n);

        // Set initial state: begin with an empty operation list
        PlanState initialState = new PlanState(pBody, Lists.<PConstraintInfo>newArrayList(), boundVariables);

        // It is needed to start from a list that is ordered by the cost of the constraint application
        OperationCostComparator infoComparator = new OperationCostComparator();
        Collections.sort(allMaskInfos, infoComparator);

        // Initial state creation, categorizes all operations; add present checks to operationsList
        initialState.updateOperations(allMaskInfos, allMaskInfos);
        stateTable.get(n).add(0, initialState);

        // stateTable.get(0) will contain the states with adornment B*
        for (int i = n; i > 0; i--) {
            for (int j = 0; j < k && j < stateTable.get(i).size(); j++) {
                PlanState currentState = stateTable.get(i).get(j);

                for (PConstraintInfo constraintInfo : currentState.getPresentExtends()) {
                    // for each present operation
                    PlanState newState = calculateNextState(currentState, constraintInfo);
                    int i2 = Sets.difference(pBody.getUniqueVariables(), newState.getBoundVariables()).size();
                    if (currentState.getBoundVariables().size() == newState.getBoundVariables().size()) {
                        // This means no variable binding was done, go on with the next constraint info
                        continue;
                    }
                    List<Integer> newIndices = determineIndices(stateTable, i2, newState, k);
                    int a = newIndices.get(0);
                    int c = newIndices.get(1);

                    if (checkInsertCondition(stateTable.get(i2), newState, reachableBoundVariableSets, a, c, k)) {
                        updateOperations(newState, currentState, constraintInfo);
                        insert(stateTable, i2, newState, a, c, k);
                    }
                }
            }
        }

        return stateTable.get(0).get(0);
    }

    private List<List<PlanState>> initializeStateTable(int k, int n) {
        List<List<PlanState>> stateTable = Lists.newArrayList();
        // Initialize state table and fill it with null
        for (int i = 0; i <= n; i++) {
            stateTable.add(Lists.<PlanState>newArrayList());
        }
        return stateTable;
    }

    private void insert(List<List<PlanState>> stateTable, int idx, PlanState newState, int a, int c, int k) {
        stateTable.get(idx).add(c, newState);
        while (stateTable.get(idx).size() > k) {
            // Truncate back to size k when grows too big
            stateTable.set(idx, stateTable.get(idx).subList(0, k));
        }
    }

    private void updateOperations(PlanState newState, PlanState currentState, PConstraintInfo constraintInfo) {
        List<PConstraintInfo> presentExtends = currentState.getPresentExtends();
        List<PConstraintInfo> futureExtends = currentState.getFutureExtends();
        // Compile the list of extend operations
        List<PConstraintInfo> extendz = merge(presentExtends, futureExtends);

        // Check operations
        newState.updateOperations(extendz, currentState.getFutureChecks());

        return;
    }

    private List<PConstraintInfo> merge(List<PConstraintInfo> presentExtensionsForState,
            List<PConstraintInfo> futureExtensionsForState) {
        int presentExtensionsIndex = 0;
        int futureExtensionsIndex = 0;
        int extensionIndex = 0;

        List<PConstraintInfo> extensions = Lists.newArrayList();

        while (presentExtensionsIndex < presentExtensionsForState.size()
                || futureExtensionsIndex < futureExtensionsForState.size()) {
            if (futureExtensionsIndex >= futureExtensionsForState.size()
                    || (presentExtensionsIndex < presentExtensionsForState.size()
                            && presentExtensionsForState.get(presentExtensionsIndex)
                                    .getCost() < futureExtensionsForState.get(futureExtensionsIndex).getCost())) {
                extensions.add(extensionIndex, presentExtensionsForState.get(presentExtensionsIndex));
                presentExtensionsIndex++;
            } else {
                extensions.add(extensionIndex, futureExtensionsForState.get(futureExtensionsIndex));
                futureExtensionsIndex++;
            }
            extensionIndex++;
        }
        return extensions;
    }

    private boolean checkInsertCondition(List<PlanState> list, PlanState newState,
            List<Set<PVariable>> reachableBoundVariableSets, int a, int c, int k) {
        //        boolean isAmongBestK = (a == (k + 1)) && c < a && reachableBoundVariableSets.contains(newState.getBoundVariables());
        boolean isAmongBestK = a == k && c < a;
        boolean isBetterThanCurrent = a < k && c <= a;

        return isAmongBestK || isBetterThanCurrent;
    }

    private List<Integer> determineIndices(List<List<PlanState>> stateTable, int i2, PlanState newState, int k) {
        int a = k;
        int c = 0;
        List<Integer> acIndices = Lists.newArrayList();
        for (int j = 0; j < k; j++) {
            if (j < stateTable.get(i2).size()) {
                PlanState stateInTable = stateTable.get(i2).get(j);
                if (newState.getBoundVariables().equals(stateInTable.getBoundVariables())) {
                    // The new state has the same adornment as the stored one - they are not adornment disjoint
                    a = j;
                }
                if (newState.getCost() >= stateInTable.getCost()) {
                    c = j + 1;
                }
            } else {
                break;
            }
        }

        acIndices.add(a);
        acIndices.add(c);
        return acIndices;
    }

    private PlanState calculateNextState(PlanState currentState, PConstraintInfo constraintInfo) {
        // Create operation list based on the current state
        ArrayList<PConstraintInfo> newOperationsList = Lists.newArrayList(currentState.getOperations());
        newOperationsList.add(constraintInfo);

        // Bound the free variables
        SetView<PVariable> allBoundVariables = Sets.union(currentState.getBoundVariables(),
                constraintInfo.getFreeVariables());
        PlanState newState = new PlanState(currentState.getAssociatedPBody(), newOperationsList,
                allBoundVariables.immutableCopy());

        return newState;
    }

    private List<Set<PVariable>> reachabilityAnalysis(PBody pBody, List<PConstraintInfo> constraintInfos) {
        // TODO implement reachability analisys, also save/persist the results somewhere
        List<Set<PVariable>> reachableBoundVariableSets = Lists.newArrayList();
        return reachableBoundVariableSets;
    }

    /**
     * Create all possible application condition for all constraint
     * 
     * @param constraintSet the set of constraints
     * @param runtimeContext the model dependent runtime contest
     * @return a collection of the wrapper PConstraintInfo objects with all the allowed application conditions
     */
    private List<PConstraintInfo> createPConstraintInfos(Set<PConstraint> constraintSet,
            IQueryRuntimeContext runtimeContext) {
        List<PConstraintInfo> constraintInfos = Lists.newArrayList();

        for (PConstraint pConstraint : constraintSet) {
            if (pConstraint instanceof ExportedParameter) {
                // Do not create mask info for exported parameter, for it is only a symbolic constraint
                continue;
            }
            if (pConstraint instanceof TypeConstraint) {
                Set<PVariable> affectedVariables = pConstraint.getAffectedVariables();
                Set<Set<PVariable>> bindings = Sets.powerSet(affectedVariables);
                doCreateConstraintInfosForTypeConstraint(runtimeContext, constraintInfos,
                        (TypeConstraint) pConstraint, affectedVariables, bindings);
            } else {
                // Create constraint infos so that only single use variables can be unbound
                Set<PVariable> affectedVariables = pConstraint.getAffectedVariables();

                Set<PVariable> singleUseVariables = Sets.newHashSet();
                for (PVariable pVariable : affectedVariables) {
                    Set<PConstraint> allReferringConstraints = pVariable.getReferringConstraints();
                    // Filter out exported parameter constraints
                    Set<ExportedParameter> referringExportedParameters = pVariable
                            .getReferringConstraintsOfType(ExportedParameter.class);
                    SetView<PConstraint> trueReferringConstraints = Sets.difference(allReferringConstraints,
                            referringExportedParameters);
                    if (trueReferringConstraints.size() == 1) {
                        singleUseVariables.add(pVariable);
                    }
                }
                SetView<PVariable> nonSingleUseVariables = Sets.difference(affectedVariables, singleUseVariables);
                // Generate bindings by taking the unioning each element of the power set with the set of non-single use variables
                Set<Set<PVariable>> singleUseVariablesPowerSet = Sets.powerSet(singleUseVariables);
                Set<Set<PVariable>> bindings = Sets.newHashSet();
                for (Set<PVariable> set : singleUseVariablesPowerSet) {
                    bindings.add(Sets.newHashSet(set));
                }
                for (Set<PVariable> set : bindings) {
                    set.addAll(nonSingleUseVariables);
                }

                if (pConstraint instanceof PatternMatchCounter) {
                    // in cases of this type, the deduced variables will contain only the result variable
                    final PVariable resultVariable = pConstraint.getDeducedVariables().iterator().next();
                    Set<Set<PVariable>> additionalBindings = Sets.newHashSet();
                    for (Set<PVariable> binding : bindings) {
                        if (binding.contains(resultVariable)) {
                            Collection<PVariable> filteredBinding = Collections2.filter(binding,
                                    new Predicate<PVariable>() {
                                        @Override
                                        public boolean apply(PVariable input) {
                                            return input != resultVariable;
                                        }
                                    });
                            additionalBindings.add(Sets.newHashSet(filteredBinding));
                        }

                    }
                    bindings.addAll(additionalBindings);
                }

                doCreateConstraintInfos(runtimeContext, constraintInfos, pConstraint, affectedVariables, bindings);
            }
        }
        return constraintInfos;
    }

    private void doCreateConstraintInfosForTypeConstraint(IQueryRuntimeContext runtimeContext,
            List<PConstraintInfo> constraintInfos, TypeConstraint typeConstraint, Set<PVariable> affectedVariables,
            Set<Set<PVariable>> bindings) {
        if (!allowInverseNavigation) {
            // When inverse navigation is not allowed, filter out operation masks, where
            // the first variable would be free AND the feature is an EReference and has no EOpposite
            bindings = excludeUnnavigableOperationMasks(typeConstraint, bindings);
        } else {
            // Also do the above case, if it is an EReference with no EOpposite, or is an EAttribute
            IInputKey inputKey = typeConstraint.getSupplierKey();
            if (inputKey instanceof EStructuralFeatureInstancesKey) {
                final EStructuralFeature feature = ((EStructuralFeatureInstancesKey) inputKey).getEmfKey();
                if (feature instanceof EReference) {
                    if (!useIndex) {
                        bindings = excludeUnnavigableOperationMasks(typeConstraint, bindings);
                    }
                } else {
                    bindings = excludeUnnavigableOperationMasks(typeConstraint, bindings);
                }
            }
        }
        doCreateConstraintInfos(runtimeContext, constraintInfos, typeConstraint, affectedVariables, bindings);
    }

    private Set<Set<PVariable>> excludeUnnavigableOperationMasks(TypeConstraint typeConstraint,
            Set<Set<PVariable>> bindings) {
        PVariable firstVariable = typeConstraint.getVariableInTuple(0);
        Iterator<Set<PVariable>> iterator = bindings.iterator();
        Set<Set<PVariable>> elementsToRemove = Sets.newHashSet();
        while (iterator.hasNext()) {
            Set<PVariable> boundVariablesSet = iterator.next();
            if (!boundVariablesSet.isEmpty() && !boundVariablesSet.contains(firstVariable)
                    && !hasEOpposite(typeConstraint)) {
                elementsToRemove.add(boundVariablesSet);
            }
        }
        bindings = Sets.difference(bindings, elementsToRemove);
        return bindings;
    }

    private boolean hasEOpposite(TypeConstraint typeConstraint) {
        IInputKey supplierKey = typeConstraint.getSupplierKey();
        if (supplierKey instanceof EStructuralFeatureInstancesKey) {
            EStructuralFeature wrappedKey = ((EStructuralFeatureInstancesKey) supplierKey).getWrappedKey();
            if (wrappedKey instanceof EReference) {
                EReference eOpposite = ((EReference) wrappedKey).getEOpposite();
                if (eOpposite != null) {
                    return true;
                }
            }
        }
        return false;
    }

    private void doCreateConstraintInfos(IQueryRuntimeContext runtimeContext, List<PConstraintInfo> constraintInfos,
            PConstraint pConstraint, Set<PVariable> affectedVariables, Set<Set<PVariable>> bindings) {
        Set<PConstraintInfo> sameWithDifferentBindings = Sets.newHashSet();
        for (Set<PVariable> boundVariables : bindings) {
            PConstraintInfo info = new PConstraintInfo(pConstraint, boundVariables,
                    Sets.difference(affectedVariables, boundVariables), sameWithDifferentBindings, runtimeContext);
            constraintInfos.add(info);
            sameWithDifferentBindings.add(info);
        }
    }

    protected EClassifier extractClassifierLiteral(String packageUriAndClassifierName) {
        int lastSlashPosition = packageUriAndClassifierName.lastIndexOf('/');
        int scopingPosition = packageUriAndClassifierName.lastIndexOf("::");
        String packageUri = packageUriAndClassifierName.substring(scopingPosition + 2, lastSlashPosition);
        String classifierName = packageUriAndClassifierName.substring(lastSlashPosition + 1);

        EPackage ePackage = EPackage.Registry.INSTANCE.getEPackage(packageUri);
        Preconditions.checkState(ePackage != null, "EPackage %s not found in EPackage Registry.", packageUri);
        EClassifier literal = ePackage.getEClassifier(classifierName);
        Preconditions.checkState(literal != null, "Classifier %s not found in EPackage %s", classifierName,
                packageUri);
        return literal;
    }

}