org.eclipse.incquery.patternlanguage.emf.specification.SpecificationBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.incquery.patternlanguage.emf.specification.SpecificationBuilder.java

Source

/*******************************************************************************
 * Copyright (c) 2010-2014, 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:
 *   Zoltan Ujhelyi - initial API and implementation
 *******************************************************************************/
package org.eclipse.incquery.patternlanguage.emf.specification;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.eclipse.incquery.patternlanguage.emf.specification.builder.EPMToPBody;
import org.eclipse.incquery.patternlanguage.emf.specification.builder.NameToSpecificationMap;
import org.eclipse.incquery.patternlanguage.emf.specification.builder.PatternBodyTransformer;
import org.eclipse.incquery.patternlanguage.emf.specification.builder.PatternSanitizer;
import org.eclipse.incquery.patternlanguage.helper.CorePatternLanguageHelper;
import org.eclipse.incquery.patternlanguage.patternLanguage.Annotation;
import org.eclipse.incquery.patternlanguage.patternLanguage.Pattern;
import org.eclipse.incquery.patternlanguage.patternLanguage.PatternBody;
import org.eclipse.incquery.runtime.api.IPatternMatch;
import org.eclipse.incquery.runtime.api.IQuerySpecification;
import org.eclipse.incquery.runtime.api.IncQueryMatcher;
import org.eclipse.incquery.runtime.exception.IncQueryException;
import org.eclipse.incquery.runtime.matchers.psystem.InitializablePQuery;
import org.eclipse.incquery.runtime.matchers.psystem.PBody;
import org.eclipse.incquery.runtime.matchers.psystem.annotations.PAnnotation;
import org.eclipse.incquery.runtime.matchers.psystem.queries.PProblem;
import org.eclipse.incquery.runtime.matchers.psystem.queries.PQuery;
import org.eclipse.incquery.runtime.matchers.psystem.queries.PQuery.PQueryStatus;
import org.eclipse.incquery.runtime.matchers.psystem.queries.QueryInitializationException;
import org.eclipse.incquery.runtime.matchers.psystem.rewriters.RewriterException;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
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;

/**
 * An instance class to initialize {@link PBody} instances from {@link Pattern} definitions. A single instance of this
 * builder is used during construction, that maintains the mapping between {@link Pattern} and {@link PQuery} objects,
 * and can be initialized with a pre-defined set of mappings.</p>
 *
 * <p>
 * The SpecificationBuilder is stateful: it stores all previously built specifications, allowing further re-use.
 *
 * @author Zoltan Ujhelyi
 *
 */
public class SpecificationBuilder {

    private NameToSpecificationMap patternMap;
    /**
     * This map is used to detect a re-addition of a pattern with a fqn that is used by a previously added pattern.
     */
    private Map<String, Pattern> patternNameMap = new HashMap<String, Pattern>();
    private Multimap<PQuery, IQuerySpecification<?>> dependantQueries = Multimaps.newSetMultimap(
            new HashMap<PQuery, Collection<IQuerySpecification<?>>>(), new Supplier<Set<IQuerySpecification<?>>>() {

                @Override
                public Set<IQuerySpecification<?>> get() {
                    return Sets.newHashSet();
                }
            });
    private PatternSanitizer sanitizer = new PatternSanitizer(/*logger*/ null /* do not log all errors */);

    /**
     * Initializes a query builder with no previously known query specifications
     */
    public SpecificationBuilder() {
        patternMap = new NameToSpecificationMap();
    }

    /**
     * Sets up a query builder with a predefined set of specifications
     */
    public SpecificationBuilder(IQuerySpecification<?>... specifications) {
        patternMap = new NameToSpecificationMap(specifications);
        processPatternSpecifications();
    }

    /**
     * Sets up a query builder with a predefined collection of specifications
     */
    public SpecificationBuilder(
            Collection<? extends IQuerySpecification<? extends IncQueryMatcher<? extends IPatternMatch>>> specifications) {
        patternMap = new NameToSpecificationMap(specifications);
        processPatternSpecifications();
    }

    public SpecificationBuilder(NameToSpecificationMap patternMap) {
        this.patternMap = patternMap;
        processPatternSpecifications();
    }

    /**
     * Processes all existing query specifications searching for possible pattern instances, and if found, add it to the
     * {@link #patternNameMap}.
     */
    private void processPatternSpecifications() {
        for (GenericQuerySpecification spec : Iterables.filter(patternMap.values(),
                GenericQuerySpecification.class)) {
            patternNameMap.put(spec.getFullyQualifiedName(), spec.getInternalQueryRepresentation().getPattern());
        }
    }

    /**
     * Creates a new or returns an existing query specification for the pattern. It is expected, that the builder will
     * not be called with different patterns having the same fqn over its entire lifecycle.
     *
     * @param pattern
     * @throws IncQueryException
     */
    public IQuerySpecification<? extends IncQueryMatcher<? extends IPatternMatch>> getOrCreateSpecification(
            Pattern pattern) throws IncQueryException {
        return getOrCreateSpecification(pattern, false);
    }

    /**
     * Creates a new or returns an existing query specification for the pattern. It is expected, that the builder will
     * not be called with different patterns having the same fqn over its entire lifecycle.
     *
     * @param pattern
     * @param skipPatternValidation
     *            if set to true, detailed pattern validation is skipped - true for model inferrer; not recommended for
     *            generic API
     * @throws IncQueryException
     */
    public IQuerySpecification<? extends IncQueryMatcher<? extends IPatternMatch>> getOrCreateSpecification(
            Pattern pattern, boolean skipPatternValidation) throws IncQueryException {
        return getOrCreateSpecification(pattern, Lists.<IQuerySpecification<?>>newArrayList(),
                skipPatternValidation);
    }

    public IQuerySpecification<? extends IncQueryMatcher<? extends IPatternMatch>> getOrCreateSpecification(
            Pattern pattern, List<IQuerySpecification<?>> createdPatternList, boolean skipPatternValidation)
            throws IncQueryException {
        Preconditions.checkArgument(pattern != null && !pattern.eIsProxy(),
                "Cannot create specification from a null pattern");
        String fqn = CorePatternLanguageHelper.getFullyQualifiedName(pattern);
        Preconditions.checkArgument(fqn != null && !"".equals(fqn), "Pattern name cannot be empty");
        Preconditions.checkArgument(!patternNameMap.containsKey(fqn) || pattern.equals(patternNameMap.get(fqn)),
                "This builder already contains a different pattern with the fqn %s of the newly added pattern.",
                fqn);
        IQuerySpecification<?> specification = getSpecification(pattern);
        if (specification == null) {
            try {
                specification = buildSpecification(pattern, skipPatternValidation, createdPatternList);
            } catch (QueryInitializationException e) {
                throw new IncQueryException(e);
            }
        }
        return specification;
    }

    protected IQuerySpecification<?> buildSpecification(Pattern pattern) throws QueryInitializationException {
        return buildSpecification(pattern, false, Lists.<IQuerySpecification<?>>newArrayList());
    }

    protected IQuerySpecification<?> buildSpecification(Pattern pattern,
            List<IQuerySpecification<?>> newSpecifications) throws QueryInitializationException {
        return buildSpecification(pattern, false, newSpecifications);
    }

    protected IQuerySpecification<?> buildSpecification(Pattern pattern, boolean skipPatternValidation,
            List<IQuerySpecification<?>> newSpecifications) throws QueryInitializationException {
        String fqn = CorePatternLanguageHelper.getFullyQualifiedName(pattern);
        Preconditions.checkArgument(!patternMap.containsKey(fqn),
                "Builder already stores query with the name of " + fqn);
        if (sanitizer.admit(pattern, skipPatternValidation)) {
            Set<Pattern> newPatterns = Sets
                    .newHashSet(Sets.filter(sanitizer.getAdmittedPatterns(), new Predicate<Pattern>() {

                        @Override
                        public boolean apply(Pattern pattern) {
                            final String name = CorePatternLanguageHelper.getFullyQualifiedName(pattern);
                            return !pattern.eIsProxy() && !"".equals(name) && !patternMap.containsKey(name);
                        }
                    }));
            // Initializing new query specifications
            for (Pattern newPattern : newPatterns) {
                String patternFqn = CorePatternLanguageHelper.getFullyQualifiedName(newPattern);
                GenericQuerySpecification specification = new GenericQuerySpecification(
                        new GenericEMFPatternPQuery(newPattern, true));
                patternMap.put(patternFqn, specification);
                patternNameMap.put(patternFqn, newPattern);
                newSpecifications.add(specification);
            }
            // Updating bodies
            for (Pattern newPattern : newPatterns) {
                String patternFqn = CorePatternLanguageHelper.getFullyQualifiedName(newPattern);
                GenericQuerySpecification specification = (GenericQuerySpecification) patternMap.get(patternFqn);
                GenericEMFPatternPQuery pQuery = specification.getInternalQueryRepresentation();
                try {
                    buildAnnotations(newPattern, pQuery);
                    buildBodies(newPattern, pQuery);
                } catch (IncQueryException e) {
                    pQuery.addError(new PProblem(e, e.getShortMessage()));
                } catch (RewriterException e) {
                    pQuery.addError(new PProblem(e, e.getShortMessage()));
                }
                if (!PQueryStatus.ERROR.equals(pQuery.getStatus())) {
                    for (PQuery query : pQuery.getDirectReferredQueries()) {
                        dependantQueries.put(query, specification);
                    }
                }
            }
        } else {
            for (Pattern rejectedPattern : sanitizer.getRejectedPatterns()) {
                String patternFqn = CorePatternLanguageHelper.getFullyQualifiedName(rejectedPattern);
                if (!patternMap.containsKey(patternFqn)) {
                    GenericQuerySpecification rejected = new GenericQuerySpecification(
                            new GenericEMFPatternPQuery(rejectedPattern, true));
                    for (PProblem problem : sanitizer.getProblemByPattern(rejectedPattern))
                        rejected.getInternalQueryRepresentation().addError(problem);
                    patternMap.put(patternFqn, rejected);
                    patternNameMap.put(patternFqn, rejectedPattern);
                    newSpecifications.add(rejected);
                }
            }
        }
        IQuerySpecification<?> specification = patternMap.get(fqn);
        if (specification == null) {
            GenericQuerySpecification erroneousSpecification = new GenericQuerySpecification(
                    new GenericEMFPatternPQuery(pattern, true));
            erroneousSpecification.getInternalQueryRepresentation()
                    .addError(new PProblem("Unable to compile pattern due to an unspecified error"));
            patternMap.put(fqn, erroneousSpecification);
            patternNameMap.put(fqn, pattern);
            newSpecifications.add(erroneousSpecification);
            specification = erroneousSpecification;
        }
        return specification;
    }

    protected void buildAnnotations(Pattern pattern, InitializablePQuery query) throws IncQueryException {
        for (Annotation annotation : pattern.getAnnotations()) {
            PAnnotation pAnnotation = new PAnnotation(annotation.getName());
            for (Entry<String, Object> attribute : CorePatternLanguageHelper
                    .evaluateAnnotationParameters(annotation).entrySet()) {
                pAnnotation.addAttribute(attribute.getKey(), attribute.getValue());
            }
            query.addAnnotation(pAnnotation);
        }
    }

    public Set<PBody> buildBodies(Pattern pattern, InitializablePQuery query) throws QueryInitializationException {
        Set<PBody> bodies = getBodies(pattern, query);
        query.initializeBodies(bodies);
        return bodies;
    }

    public Set<PBody> getBodies(Pattern pattern, PQuery query) throws QueryInitializationException {
        PatternBodyTransformer transformer = new PatternBodyTransformer(pattern);
        Set<PBody> pBodies = Sets.newLinkedHashSet();
        for (PatternBody body : pattern.getBodies()) {
            EPMToPBody acceptor = new EPMToPBody(pattern, query, patternMap);
            PBody pBody = transformer.transform(body, acceptor);
            pBodies.add(pBody);
        }
        return pBodies;
    }

    public IQuerySpecification<?> getSpecification(Pattern pattern) {
        String fqn = CorePatternLanguageHelper.getFullyQualifiedName(pattern);
        return getSpecification(fqn);
    }

    public IQuerySpecification<?> getSpecification(String fqn) {
        return patternMap.get(fqn);
    }

    /**
     * Forgets a specification in the builder. </p>
     * <p>
     * <strong>Warning!</strong> Removing a specification does not change any specification created previously, even if
     * they are referring to the old version of the specification. Only use this if you are sure all dependant queries
     * are also removed, otherwise use {@link #forgetSpecificationTransitively(IQuerySpecification)} instead.
     *
     */
    public void forgetSpecification(IQuerySpecification<?> specification) {
        String fqn = specification.getFullyQualifiedName();
        patternMap.remove(fqn);
        if (specification instanceof GenericQuerySpecification) {
            patternNameMap.remove(fqn);
            sanitizer.forgetPattern(
                    ((GenericQuerySpecification) specification).getInternalQueryRepresentation().getPattern());
        }
    }

    private void forgetSpecificationTransitively(IQuerySpecification<?> specification,
            Set<IQuerySpecification<?>> forgottenSpecifications) {
        forgetSpecification(specification);
        forgottenSpecifications.add(specification);
        for (IQuerySpecification<?> dependant : dependantQueries
                .get(specification.getInternalQueryRepresentation())) {
            if (!forgottenSpecifications.contains(dependant)) {
                forgetSpecificationTransitively(dependant, forgottenSpecifications);
            }
        }
        dependantQueries.removeAll(specification);
    }

    /**
     * Forgets a specification in the builder, and also removes anything that depends on it.
     *
     * @param specification
     * @returns the set of specifications that were removed from the builder
     */
    public Set<IQuerySpecification<?>> forgetSpecificationTransitively(IQuerySpecification<?> specification) {
        Set<IQuerySpecification<?>> forgottenSpecifications = Sets.newHashSet();
        forgetSpecificationTransitively(specification, forgottenSpecifications);
        return forgottenSpecifications;
    }
}