/* * #! * Ontopia Engine * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * 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 net.ontopia.topicmaps.query.impl.utils; import java.util.Iterator; import java.util.List; import java.util.Map; import net.ontopia.topicmaps.core.TopicIF; import net.ontopia.topicmaps.query.core.InvalidQueryException; import net.ontopia.topicmaps.query.parser.NotClause; import net.ontopia.topicmaps.query.parser.OrClause; import net.ontopia.topicmaps.query.parser.Pair; import net.ontopia.topicmaps.query.parser.PredicateClause; import net.ontopia.topicmaps.query.parser.PredicateIF; import net.ontopia.topicmaps.query.parser.TologQuery; /** * INTERNAL: Analyzes the types of variables in the query. Finds the * type(s) of each variable and reports typing errors. */ public class QueryAnalyzer { public static BindingContext analyzeTypes(TologQuery query) throws InvalidQueryException { boolean strict = query.getOptions().getBooleanValue("compiler.typecheck"); BindingContext bc = new BindingContext(strict); analyzeTypes(query.getClauses(), bc); return bc; } public static BindingContext analyzeTypes(List clauses, boolean strict) throws InvalidQueryException { BindingContext bc = new BindingContext(strict); analyzeTypes(clauses, bc); return bc; } public static void analyzeTypes(List clauses, BindingContext bc) throws InvalidQueryException { // --- HOW IT WORKS // predicate clauses define the types of their arguments directly // the allowed types for the same variable from different // predicates in the same horn clause (AND list) are combined with // INTERSECTION // the allowed types for the same variable from different branches // of an OR clause are combined with UNION // the allowed types for the same variable outside and inside an // OPTIONAL clause are as given from the outside, except if // nothing is given outside, in which case they are as given // inside // the allowed types for the same variable outside and inside a // NOT clause are combined with INTERSECTION // Process clauses contexts; OR, NOT (if any) Iterator iter = clauses.iterator(); while (iter.hasNext()) { Object clause = iter.next(); if (clause instanceof PredicateClause) { // Analyze the predicate and its arguments PredicateClause pc = (PredicateClause) clause; analyzeArguments(pc, bc); } else if (clause instanceof OrClause) { List alternatives = ((OrClause) clause).getAlternatives(); BindingContext bc1 = new BindingContext(bc.getCheckingTypes()); if (alternatives.size() == 1) { // optional clause List aclauses = (List) alternatives.get(0); analyzeTypes(aclauses, bc1); bc.mergeAssymetric(bc1); } else { // ordinary OR clause Iterator alts = alternatives.iterator(); while(alts.hasNext()) { // Each alternative introduces a new binding context BindingContext bc2 = new BindingContext(bc.getCheckingTypes()); List aclauses = (List)alts.next(); analyzeTypes(aclauses, bc2); // merge bc2 with parent bc1 bc1.mergeUnion(bc2); } // merge bc1 with parent bc bc.mergeIntersect(bc1); } } else if (clause instanceof NotClause) { NotClause nclause = (NotClause) clause; // Not clause introduces a new binding context BindingContext bc1 = new BindingContext(bc.getCheckingTypes()); analyzeTypes(nclause.getClauses(), bc1); bc.mergeAssymetric(bc1); } } } private static final Class[] TYPES_TOPIC = { TopicIF.class }; private static void analyzeArguments(PredicateClause clause, BindingContext bc) throws InvalidQueryException { PredicateIF predicate = clause.getPredicate(); PredicateSignature sign = PredicateSignature.getSignature(predicate); Object[] args = clause.getArguments().toArray(); sign.validateArguments(args, predicate.getName(), bc.getCheckingTypes()); for (int ix = 0; ix < args.length; ix++) { Class[] types = sign.getTypes(ix); if (types.length == 1 && types[0].equals(Pair.class)) types = TYPES_TOPIC; bc.addArgumentTypes(args[ix], types, predicate); } } /** * Verifies that all used parameters are specified and that they are * of the correct types. We don't care if too many arguments are * specified so long as all the ones used in the query are right. */ public static void verifyParameters(TologQuery query, Map arguments) throws InvalidQueryException { boolean typecheck = query.getOptions().getBooleanValue("compiler.typecheck"); Map ptypes = query.getParameterTypes(); Iterator it = ptypes.keySet().iterator(); while (it.hasNext()) { String parname = (String) it.next(); Object value = (arguments == null ? null : arguments.get(parname)); if (value == null) throw new InvalidQueryException("Parameter " + parname + " not specified"); boolean ok = false; Object[] types = (Object[]) ptypes.get(parname); for (int ix = 0; !ok && ix < types.length; ix++) { Class type = (Class) types[ix]; ok = type.isAssignableFrom(value.getClass()); } if (!ok & typecheck) throw new InvalidQueryException( "Parameter " + parname + " must be " + PredicateSignature.getClassList(types) + ", but was " + value); } } }