Java tutorial
/* * Copyright 2013 SFB 632. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package annis.ql.parser; import annis.exceptions.AnnisQLSemanticsException; import annis.model.QueryNode; import annis.model.Join; import annis.sqlgen.model.NonBindingJoin; import com.google.common.base.Joiner; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multiset; import com.google.common.collect.TreeMultiset; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; /** * Performs semantic checks on the parsed query. * * <ul> * <li>checks if there is no search expression at all</li> * <li>no binary linguistic relations are allowed if there is only one node</li> * <li>checks if all nodes of an alternative are connected</li> * <li>every node variable name should be given only once in an alternative</li> * </ul> * * @author Thomas Krause <krauseto@hu-berlin.de> */ public class SemanticValidator implements QueryDataTransformer { @Override public QueryData transform(QueryData data) { int i = 1; for (List<QueryNode> alternative : data.getAlternatives()) { checkAlternative(data, alternative, i++); } // we actually don't manipulate the output return data; } public void checkAlternative(QueryData data, List<QueryNode> alternative, int alternativeIndex) { // check if there is at least one search expression if (alternative.isEmpty()) { throw new AnnisQLSemanticsException("Missing search expression."); } // there are not linguistic binary relations allowed if there is only one node if (alternative.size() == 1) { QueryNode n = alternative.get(0); for (Join j : n.getOutgoingJoins()) { if (j.getTarget() != null) { throw new AnnisQLSemanticsException( "No binary linguistic relations allowed if there is only one node in query."); } } } // get all nodes connected to the first one Multimap<Long, QueryNode> connected = calculateConnected(alternative); Set<Long> transitiveHull = new HashSet<>(); transitiveHull.add(alternative.get(0).getId()); createTransitiveHull(alternative.get(0), connected, transitiveHull); Multiset<String> variableNames = TreeMultiset.create(); Set<Long> unconnectedNodes = new HashSet<>(); for (QueryNode n : alternative) { unconnectedNodes.add(n.getId()); variableNames.add(n.getVariable()); } unconnectedNodes.removeAll(transitiveHull); // check if each node is contained in the connected nodes if (!unconnectedNodes.isEmpty()) { List<String> variables = new LinkedList<>(); for (QueryNode n : alternative) { if (unconnectedNodes.contains(n.getId())) { variables.add(n.getVariable()); } } if (alternative.size() == 1) { throw new AnnisQLSemanticsException("Variable(s) [" + Joiner.on(",").join(variables) + "] not bound (use linguistic operators)."); } else { throw new AnnisQLSemanticsException("Variable(s) [" + Joiner.on(",").join(variables) + "] not bound in alternative " + alternativeIndex + "(use linguistic operators). " + "Normalized query is: \n" + data.toAQL()); } } // check if any variable name was given more than once List<String> invalidNames = new LinkedList<>(); for (Multiset.Entry<String> e : variableNames.entrySet()) { if (e.getCount() > 1) { invalidNames.add(e.getElement()); } } if (!invalidNames.isEmpty()) { throw new AnnisQLSemanticsException( "The following variable names are " + "used for more than one node: " + Joiner.on(", ").join(invalidNames) + "\nNormalized Query is: \n" + data.toAQL()); } } private Multimap<Long, QueryNode> calculateConnected(List<QueryNode> nodes) { Multimap<Long, QueryNode> result = HashMultimap.create(); for (QueryNode n : nodes) { for (Join j : n.getOutgoingJoins()) { if (j.getTarget() != null && !(j instanceof NonBindingJoin)) { long left = n.getId(); long right = j.getTarget().getId(); result.put(left, j.getTarget()); result.put(right, n); } } } return result; } private void createTransitiveHull(QueryNode n, final Multimap<Long, QueryNode> connected, Set<Long> transitiveHull) { Collection<QueryNode> outgoing = connected.get(n.getId()); if (outgoing != null) { for (QueryNode otherNode : outgoing) { if (transitiveHull.add(otherNode.getId())) { createTransitiveHull(otherNode, connected, transitiveHull); } } } } }