All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.xtext.validation.impl.ConcreteSyntaxConstraintProvider Maven / Gradle / Ivy

There is a newer version: 2.4.3
Show newest version
/*******************************************************************************
 * Copyright (c) 2010 itemis AG (http://www.itemis.eu) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.eclipse.xtext.validation.impl;

import static org.eclipse.xtext.GrammarUtil.*;

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

import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Action;
import org.eclipse.xtext.Alternatives;
import org.eclipse.xtext.Assignment;
import org.eclipse.xtext.CompoundElement;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.Grammar;
import org.eclipse.xtext.GrammarUtil;
import org.eclipse.xtext.Group;
import org.eclipse.xtext.IGrammarAccess;
import org.eclipse.xtext.ParserRule;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.UnorderedGroup;
import org.eclipse.xtext.util.Pair;
import org.eclipse.xtext.util.Tuples;
import org.eclipse.xtext.validation.IConcreteSyntaxConstraintProvider;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.google.inject.Singleton;

/**
 * @author Moritz Eysholdt - Initial contribution and API
 */
@Singleton
public class ConcreteSyntaxConstraintProvider implements IConcreteSyntaxConstraintProvider {
	public static class SyntaxConstraintNode implements ISyntaxConstraint {
		protected ISyntaxConstraint container = null;
		protected List contents;
		protected AbstractElement element;
		protected boolean multiple = false;
		protected boolean optional = false;
		protected EClass semanticType = null;
		protected Set semanticTypes = UNINITIALIZED;
		protected ConstraintType type;

		protected SyntaxConstraintNode() {
		}

		public SyntaxConstraintNode(ConstraintType type, AbstractElement ele, List contents,
				EClass semanticType, boolean multiple, boolean optional) {
			super();
			if (type == null)
				throw new NullPointerException("type must not be null");
			this.type = type;
			this.element = ele;
			this.semanticType = semanticType;
			this.multiple = multiple;
			this.optional = optional;
			this.contents = contents;
			for (ISyntaxConstraint e : contents)
				((SyntaxConstraintNode) e).container = this;
		}

		protected boolean containsType() {
			for (ISyntaxConstraint c : getContents()) {
				SyntaxConstraintNode n = (SyntaxConstraintNode) c;
				if (n.semanticType != null || n.containsType())
					return true;
			}
			return false;
		}

		public boolean dependsOn(ISyntaxConstraint ele) {
			ISyntaxConstraint cnt = findCommonContainer(ele);
			while (ele != cnt) {
				if (ele.isOptional())
					return false;
				ele = ele.getContainer();
			}
			return true;
		}

		@Override
		public boolean equals(Object obj) {
			if (obj instanceof SyntaxConstraintNode)
				return ((SyntaxConstraintNode) obj).element == element;
			return false;
		}

		public ISyntaxConstraint findCommonContainer(ISyntaxConstraint obj1) {
			ISyntaxConstraint cnt1 = obj1;
			while (cnt1 != null) {
				ISyntaxConstraint cnt2 = this;
				while (cnt2 != null) {
					if (cnt1.equals(cnt2))
						return cnt1;
					cnt2 = cnt2.getContainer();
				}
				cnt1 = cnt1.getContainer();
			}
			return null;
		}

		protected Pair, Set> getAllSemanticTypesPairs(Set exclude) {
			Set mandatory = Sets.newHashSet();
			Set optional = Sets.newHashSet();
			boolean allChildrenContributeMandatoryType = !getContents().isEmpty();
			for (ISyntaxConstraint sc : getContents())
				if (exclude == null || !exclude.contains(sc)) {
					Pair, Set> t = ((SyntaxConstraintNode) sc).getAllSemanticTypesPairs(exclude);
					if (sc.isOptional()) {
						optional.addAll(t.getFirst());
						optional.addAll(t.getSecond());
						allChildrenContributeMandatoryType = false;
					} else {
						mandatory.addAll(t.getFirst());
						optional.addAll(t.getSecond());
						if (t.getFirst().isEmpty())
							allChildrenContributeMandatoryType = false;
					}
				}
			if ((isRoot() && isOptional())
					|| (type == ConstraintType.ALTERNATIVE && !allChildrenContributeMandatoryType)) {
				optional.addAll(mandatory);
				mandatory.clear();
			}
			if (semanticType != null) {
				if (mandatory.isEmpty()
						&& (optional.isEmpty() || (optional.size() == 1 && optional.contains(semanticType))))
					mandatory.add(semanticType);
				else
					optional.add(semanticType);
			}
			if (exclude == null && !isRoot() && mandatory.isEmpty() && optional.isEmpty())
				optional.addAll(((SyntaxConstraintNode) getContainer()).getSemanticTypeByParent(Sets
						. newHashSet(this)));
			return Tuples.create(mandatory, optional);
		}

		public EStructuralFeature getAssignmentFeature(EClass clazz) {
			String name = getAssignmentName();
			EStructuralFeature f = clazz.getEStructuralFeature(name);
			if (f == null)
				throw new RuntimeException("Feature " + name + " not found for " + clazz.getName());
			return f;
		}

		public String getAssignmentName() {
			if (type != ConstraintType.ASSIGNMENT)
				throw new RuntimeException("Constraint '" + this + "' is not an assignment, but a " + getType());
			return ((Assignment) element).getFeature();
		}

		public String getCardinality() {
			return optional ? (multiple ? "*" : "?") : (multiple ? "+" : "");
		}

		public ISyntaxConstraint getContainer() {
			return container;
		}

		public List getContents() {
			return contents;
		}

		public AbstractElement getGrammarElement() {
			return element;
		}

		protected Set getSemanticTypeByParent(Set exclude) {
			if (type == ConstraintType.ALTERNATIVE) {
				exclude.addAll(getContents());
				if (semanticType != null)
					return Sets.newHashSet(semanticType);
			} else {
				Pair, Set> types = getAllSemanticTypesPairs(exclude);
				if (!types.getFirst().isEmpty())
					return types.getFirst();
				if (isRoot())
					return types.getSecond();
			}
			return ((SyntaxConstraintNode) getContainer()).getSemanticTypeByParent(exclude);
		}

		public Set getSemanticTypes() {
			Pair, Set> types = getAllSemanticTypesPairs(null);
			return !types.getFirst().isEmpty() ? types.getFirst() : types.getSecond();
		}

		public Set getSemanticTypesToCheck() {
			if (semanticTypes == UNINITIALIZED) {
				semanticTypes = getSemanticTypes();
				if (semanticTypes.isEmpty()
						|| (!isRoot() && semanticTypes.equals(((SyntaxConstraintNode) getContainer())
								.getSemanticTypes())))
					semanticTypes = null;
			}
			return semanticTypes;
		}

		public ConstraintType getType() {
			return type;
		}

		@Override
		public int hashCode() {
			return element.hashCode();
		}

		public boolean isMultiple() {
			return multiple;
		}

		public boolean isOptional() {
			return optional;
		}

		public boolean isRoot() {
			return container == null;
		}

		@Override
		public String toString() {
			return toString(null);
		}

		public String toString(final Map postfix) {
			// String t = getSemanticTypes() == null ? "" : getSemanticTypes().getName() + ":";
			String p = postfix != null && postfix.containsKey(this) ? postfix.get(this) : "";
			Iterable contents = Iterables.transform(getContents(), new Function() {
				public String apply(ISyntaxConstraint from) {
					return from.toString(postfix);
				}
			});
			switch (getType()) {
				case ASSIGNMENT:
					return /*t +*/((Assignment) element).getFeature() + p + getCardinality();
				case GROUP:
					return /*t +*/"(" + Joiner.on(" ").join(contents) + ")" + p + getCardinality();
				case ALTERNATIVE:
					return /*t +*/"(" + Joiner.on("|").join(contents) + ")" + p + getCardinality();
				case ACTION:
					return "{" + ((Action) element).getType().getClassifier().getName() + "}" + p;
			}
			return "";
		}
	}

	protected final static Set UNINITIALIZED = Sets.newHashSet();

	protected Grammar grammar;

	protected final ISyntaxConstraint INVALID_RULE = new SyntaxConstraintNode();

	protected Map rule2element = Maps.newHashMap();

	protected Map> type2Elements = Maps.newHashMap();

	protected Set validRules;

	protected void collectReachableRules(ParserRule pr, Set rules, Set visited) {
		if (!visited.add(pr))
			return;
		for (RuleCall rc : GrammarUtil.containedRuleCalls(pr))
			if (isParserRule(rc.getRule())) {
				if (GrammarUtil.containingAssignment(rc) != null)
					rules.add((ParserRule) rc.getRule());
				collectReachableRules((ParserRule) rc.getRule(), rules, visited);
			}
	}

	protected boolean containsRelevantElement(AbstractElement ele) {
		Iterator i = Iterators.concat(Collections.singleton(ele).iterator(), ele.eAllContents());
		while (i.hasNext()) {
			EObject o = i.next();
			if (o instanceof Action || o instanceof Assignment)
				return true;
			if (o instanceof RuleCall && containsRelevantElement(((RuleCall) o).getRule().getAlternatives()))
				return true;
		}
		return false;
	}

	protected ISyntaxConstraint createElement(ConstraintType type, AbstractElement ele, EClass semanticType,
			boolean multiple, boolean optional) {
		List ctns = ele instanceof CompoundElement ? ((CompoundElement) ele).getElements() : null;
		return createElement(type, ele, ctns, new ArrayList(), semanticType, multiple, optional);
	}

	protected ISyntaxConstraint createElement(ConstraintType type, AbstractElement ele,
			List lazyContents, List contents, EClass semanticType,
			boolean multiple, boolean optional) {
		if (lazyContents != null)
			for (EObject obj : lazyContents) {
				ISyntaxConstraint e = createElement(obj);
				if (e != null)
					contents.add(e);
			}
		return new SyntaxConstraintNode(type, ele, contents, semanticType, multiple, optional);
	}

	protected ISyntaxConstraint createElement(EObject obj) {
		if (!(obj instanceof AbstractElement))
			return null;
		AbstractElement ele = (AbstractElement) obj;
		boolean multiple = false;
		boolean optional = false;
		EClass semanticType = null;
		while (true) {
			multiple = multiple || isMultipleCardinality(ele);
			optional = optional || isOptionalCardinality(ele);
			if (ele.eContainer() instanceof ParserRule
					&& ((ParserRule) ele.eContainer()).getType().getClassifier() instanceof EClass)
				semanticType = (EClass) ((ParserRule) ele.eContainer()).getType().getClassifier();
			if (ele instanceof Assignment) {
				return createElement(ConstraintType.ASSIGNMENT, ele, semanticType, multiple, optional);
			} else if (ele instanceof Group || ele instanceof UnorderedGroup) {
				CompoundElement comp = (CompoundElement) ele;
				AbstractElement lastChild = null;
				for (AbstractElement o : comp.getElements())
					if (containsRelevantElement(o)) {
						if (lastChild == null)
							lastChild = o;
						else {
							List c = new ArrayList(comp.getElements());
							List e = createSummarizedAssignments(comp, c, semanticType, optional);
							if (e.size() == 1 && c.size() == 0)
								return e.get(0);
							return createElement(ConstraintType.GROUP, ele, c, e, semanticType, multiple, optional);
						}
					}
				if (lastChild == null)
					return null;
				ele = lastChild;
				continue;
			} else if (ele instanceof Alternatives) {
				int relevantChildren = 0;
				AbstractElement lastChild = null;
				for (AbstractElement o : ((CompoundElement) ele).getElements())
					if (containsRelevantElement(o)) {
						relevantChildren++;
						lastChild = o;
					}
				if (relevantChildren < ((CompoundElement) ele).getElements().size())
					optional = true;
				if (relevantChildren > 1)
					return createElement(ConstraintType.ALTERNATIVE, ele, semanticType, multiple, optional);
				if (lastChild == null)
					return null;
				ele = lastChild;
				continue;
			} else if (ele instanceof Action) {
				semanticType = (EClass) ((Action) ele).getType().getClassifier();
				return createElement(ConstraintType.ACTION, ele, semanticType, multiple, optional);
			} else if (ele instanceof RuleCall) {
				AbstractRule rule = ((RuleCall) ele).getRule();
				if (rule.getType().getClassifier() instanceof EClass) {
					ele = rule.getAlternatives();
					continue;
				}
			}
			return null;
		}
	}

	protected List createSummarizedAssignments(CompoundElement group,
			List candidates, EClass semanticType, boolean optional) {
		Multimap feature2ass = HashMultimap.create();
		Multimap feature2child = HashMultimap.create();
		for (AbstractElement c : candidates) {
			TreeIterator i = EcoreUtil2.eAll(c);
			while (i.hasNext()) {
				EObject obj = i.next();
				if (obj instanceof RuleCall || obj instanceof Action || obj instanceof Alternatives)
					return Lists.newArrayList();
				else if (obj instanceof Group) {
					Set names = Sets.newHashSet();
					for (Assignment ass : EcoreUtil2.getAllContentsOfType(obj, Assignment.class))
						names.add(ass.getFeature());
					if (names.size() > 1)
						i.prune();
				} else if (obj instanceof Assignment) {
					Assignment a = (Assignment) obj;
					feature2ass.put(a.getFeature(), a);
					feature2child.put(a.getFeature(), c);
					i.prune();
				}
			}
		}
		List result = Lists.newArrayList();
		for (Map.Entry> ent : feature2ass.asMap().entrySet()) {
			if (ent.getValue().size() < 2 || feature2child.get(ent.getKey()).size() < 2)
				continue;
			int required = 0, multiplies = 0;
			for (Assignment assignment : ent.getValue()) {
				AbstractElement e = assignment;
				while (e != group)
					if (isMultipleCardinality(e)) {
						multiplies++;
						break;
					} else
						e = (AbstractElement) e.eContainer();
				e = assignment;
				while (e != group)
					if (isOptionalCardinality(e))
						break;
					else
						e = (AbstractElement) e.eContainer();
				if (e == group)
					required++;
			}
			if (required > 1 || multiplies < 1)
				continue;
			candidates.removeAll(feature2child.get(ent.getKey()));
			optional = optional || required < 1;
			result.add(createElement(ConstraintType.ASSIGNMENT, ent.getValue().iterator().next(), semanticType, true,
					optional));
		}
		return result;
	}

	public ISyntaxConstraint getConstraint(ParserRule rule) {
		ISyntaxConstraint e = rule2element.get(rule);
		if (e == null) {
			if (isValidateableRule(rule))
				e = createElement(rule.getAlternatives());
			else
				e = INVALID_RULE;
			rule2element.put(rule, e);
		}
		return e != INVALID_RULE ? e : null;
	}

	public Collection getConstraints(EClass cls) {
		List eles = type2Elements.get(cls);
		if (eles != null)
			return eles;
		eles = Lists.newArrayList();
		for (ParserRule r : getValidRules()) {
			if (((EClass) r.getType().getClassifier()).isSuperTypeOf(cls)) {
				ISyntaxConstraint e = getConstraint(r);
				if (e != null)
					eles.add(e);
				else {
					eles.clear();
					break;
				}
			}
		}
		type2Elements.put(cls, eles);
		return eles;
	}

	protected ParserRule getFirstParserRule(Grammar grammar) {
		for (AbstractRule r : grammar.getRules())
			if (isParserRule(r))
				return (ParserRule) r;
		throw new RuntimeException("Grammar " + grammar.getName() + " contains no parser rules");
	}

	protected Set getValidRules() {
		if (validRules != null)
			return validRules;
		validRules = Sets.newHashSet();
		ParserRule first = getFirstParserRule(grammar);
		validRules.add(first);
		collectReachableRules(first, validRules, new HashSet());
		return validRules;
	}

	protected boolean isParserRule(AbstractRule rule) {
		return rule instanceof ParserRule && !GrammarUtil.isDatatypeRule((ParserRule) rule);
	}

	protected boolean isValidateableRule(ParserRule rule) {
		return !ruleContainsAssignedAction(rule, new HashSet())
				&& !ruleContainsRecursiveUnassignedRuleCall(rule, new HashSet());
	}

	protected boolean ruleContainsAssignedAction(AbstractRule rule, Set visited) {
		if (!visited.add(rule))
			return false;
		TreeIterator i = rule.eAllContents();
		while (i.hasNext()) {
			EObject o = i.next();
			if (o instanceof Action && ((Action) o).getFeature() != null)
				return true;
			else if (o instanceof Assignment)
				i.prune();
			else if (o instanceof RuleCall && isParserRule(((RuleCall) o).getRule())) {
				if (ruleContainsAssignedAction(((RuleCall) o).getRule(), visited))
					return true;
			}
		}
		return false;
	}

	protected boolean ruleContainsRecursiveUnassignedRuleCall(AbstractRule rule, Set visited) {
		if (!visited.add(rule))
			return true;
		TreeIterator i = rule.eAllContents();
		while (i.hasNext()) {
			EObject o = i.next();
			if (o instanceof Assignment)
				i.prune();
			else if (o instanceof RuleCall && isParserRule(((RuleCall) o).getRule())) {
				if (ruleContainsRecursiveUnassignedRuleCall(((RuleCall) o).getRule(), visited))
					return true;
			}
		}
		return false;
	}

	@Inject
	protected void setGrammar(IGrammarAccess grammar) {
		this.grammar = grammar.getGrammar();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy