/*
* #!
* 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.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import net.ontopia.topicmaps.query.core.InvalidQueryException;
import net.ontopia.topicmaps.query.parser.Pair;
import net.ontopia.topicmaps.query.parser.Parameter;
import net.ontopia.topicmaps.query.parser.PredicateIF;
import net.ontopia.topicmaps.query.parser.Variable;
import net.ontopia.utils.CompactHashSet;
/**
* INTERNAL.
*/
public class BindingContext {
// whether types are checked for correctness
protected boolean typecheck;
// Variable types: varname : Class[]
protected Map vtypes;
// Variable type theories: varname : Class[]
protected Map vtypetheory; // see assymetric merging below
// Parameter types: parname : Class[]
protected Map ptypes;
// Parameter type theories: parname : Class[]
protected Map ptypetheory; // see assymetric merging below
public BindingContext(boolean typecheck) {
this.typecheck = typecheck;
this.vtypes = new HashMap();
this.ptypes = new HashMap();
this.vtypetheory = new HashMap();
this.ptypetheory = new HashMap();
}
public boolean getCheckingTypes() {
return typecheck;
}
public Map getVariableTypes() {
return mergeMaps(vtypes, vtypetheory);
}
public Map getParameterTypes() {
return mergeMaps(ptypes, ptypetheory);
}
private Map mergeMaps(Map main, Map theory) {
Map merged = new HashMap(theory);
Iterator it = main.keySet().iterator();
while (it.hasNext()) {
Object k = it.next();
merged.put(k, main.get(k));
}
return merged;
}
// PredicateIF callbacks
public void addArgumentTypes(Object argument, Class[] types, PredicateIF predicate)
throws InvalidQueryException {
if (argument instanceof Pair)
argument = ((Pair) argument).getFirst();
if (argument instanceof Variable)
_addVariableTypes(((Variable) argument).getName(), types, predicate);
else if (argument instanceof Parameter)
_addParameterTypes(((Parameter) argument).getName(), types, predicate);
// else literal, so we don't care
}
// FIXME:
// would be nice to merge _addVariableTypes and _addParameterTypes;
// this makes for slight difficulties with the error messages, however.
// solution: common superclass for parameters and variables (or even
// merge them into one class with a flag to tell you what it is)
private void _addVariableTypes(String varname, Object[] types,
PredicateIF predicate)
throws InvalidQueryException {
// Register variable types in this context
Object[] newtypes = types;
Object[] etypes = (Object[]) vtypes.get(varname);
if (etypes != null) {
newtypes = intersect(etypes, types);
checkForTypeConflict("$" + varname, etypes, types, newtypes, predicate);
}
vtypes.put(varname, newtypes);
}
private void _addParameterTypes(String parname, Object[] types,
PredicateIF predicate)
throws InvalidQueryException {
// Register parameter types in this context
Object[] newtypes = types;
Object[] etypes = (Object[]) ptypes.get(parname);
if (etypes != null) {
newtypes = intersect(etypes, types);
checkForTypeConflict("%" + parname + "%", etypes, types, newtypes, predicate);
}
ptypes.put(parname, newtypes);
}
private void checkForTypeConflict(String name, Object[] types1, Object[] types2,
Object[] newtypes, PredicateIF predicate)
throws InvalidQueryException {
if (newtypes.length == 0 && typecheck)
throw new InvalidQueryException("Type conflict on " + name + ": cannot " +
"be both " +
PredicateSignature.getClassList(types1) +
" and, as required by " +
"predicate '" + predicate.getName() + "', " +
PredicateSignature.getClassList(types2));
}
// --- Modifiers
// no need to take theories into account here; child theories get discarded anyway
public void mergeIntersect(BindingContext bc)
throws InvalidQueryException {
this.vtypes = mergeTypeMapsIntersect(this.vtypes, bc.vtypes, true);
this.ptypes = mergeTypeMapsIntersect(this.ptypes, bc.ptypes, false);
this.vtypetheory = mergeTypeMapsIntersect(this.vtypetheory, bc.vtypetheory, true);
this.ptypetheory = mergeTypeMapsIntersect(this.ptypetheory, bc.ptypetheory, false);
}
protected Map mergeTypeMapsIntersect(Map map1, Map map2, boolean variables)
throws InvalidQueryException {
Map result = new HashMap(map1);
Iterator iter = map2.keySet().iterator();
while (iter.hasNext()) {
Object key = iter.next();
if (map1.containsKey(key)) {
// Merge type lists
Object[] types1 = (Object[])map1.get(key);
Object[] types2 = (Object[])map2.get(key);
Object[] newtypes = intersect(types1, types2);
if (newtypes.length == 0 && typecheck) {
String name = "$" + key;
if (!variables)
name = "%" + key + "%";
throw new InvalidQueryException("Type conflict on " + name + ": cannot " +
"be both " +
PredicateSignature.getClassList(types1) +
" and " +
PredicateSignature.getClassList(types2));
}
result.put(key, newtypes);
} else
result.put(key, (Object[]) map2.get(key));
}
return result;
}
// no need to take theories into account here; child theories get discarded anyway
public void mergeUnion(BindingContext bc) {
this.vtypes = mergeTypeMapsUnion(this.vtypes, bc.vtypes);
this.ptypes = mergeTypeMapsUnion(this.ptypes, bc.ptypes);
this.vtypetheory = mergeTypeMapsUnion(this.vtypetheory, bc.vtypetheory);
this.ptypetheory = mergeTypeMapsUnion(this.ptypetheory, bc.ptypetheory);
}
protected Map mergeTypeMapsUnion(Map map1, Map map2) {
Map result = new HashMap(map1);
Iterator iter = map2.keySet().iterator();
while (iter.hasNext()) {
Object key = iter.next();
if (map1.containsKey(key)) {
// Merge type lists
Object[] types1 = (Object[]) map1.get(key);
Object[] types2 = (Object[]) map2.get(key);
result.put(key, union(types1, types2));
} else
result.put(key, (Object[])map2.get(key));
}
return result;
}
// type information in this bc is certain, whereas type information
// from the other context is just a theory. information from the
// other bc therefore has to yield to information from this one
// whenever there is any, whereas when there is none in this one
// information is retained as a theory. information from different
// theories is unioned.
public void mergeAssymetric(BindingContext bc) {
mergeTypeMapsAssymetric(vtypes, bc.vtypes, vtypetheory, bc.vtypetheory);
mergeTypeMapsAssymetric(ptypes, bc.ptypes, ptypetheory, bc.ptypetheory);
}
// updates map1 and theory1
protected void mergeTypeMapsAssymetric(Map map1, Map map2,
Map theory1, Map theory2) {
// do the certain stuff first
Iterator iter = map2.keySet().iterator();
while (iter.hasNext()) {
Object key = iter.next();
Object[] types1 = (Object[]) map1.get(key);
Object[] types2 = (Object[]) map2.get(key);
if (!map1.containsKey(key)) {
Object[] existing = (Object[]) theory1.get(key);
if (existing != null)
types2 = union(existing, types2);
theory1.put(key, types2);
}
}
// then deal with the theory (bug #1606)
iter = theory2.keySet().iterator();
while (iter.hasNext()) {
Object key = iter.next();
// if nothing's known about this variable, record it as a theory
if (!map1.containsKey(key) && !theory1.containsKey(key))
theory1.put(key, theory2.get(key));
}
}
// --- Set operations
// RULES:
// - all types exclude each other, except Object, which excludes itself
// - Object will only ever appear alone
protected static Object[] intersect(Object[] array1, Object[] array2) {
if (array1.length == 1 && array1[0].equals(Object.class))
return array2;
if (array2.length == 1 && array2[0].equals(Object.class))
return array1;
int matches = 0;
// Result array cannot be bigger than the shortest input array
Object[] tresult = new Object[Math.min(array1.length, array2.length)];
// Look up array2 objects
for (int i = 0; i < array2.length; i++) {
boolean found = false;
for (int ix = 0; !found && ix < array1.length; ix++)
found = array2[i].equals(array1[ix]);
if (found) {
tresult[matches] = array2[i];
matches++;
}
}
Object[] result = new Object[matches];
System.arraycopy(tresult, 0, result, 0, matches);
return result;
}
protected static Object[] union(Object[] array1, Object[] array2) {
Set result = new CompactHashSet();
boolean seenobject = false;
for (int i=0; i < array1.length; i++) {
seenobject |= array1[i].equals(Object.class);
result.add(array1[i]);
}
for (int i=0; i < array2.length; i++) {
seenobject |= array2[i].equals(Object.class);
result.add(array2[i]);
}
if (seenobject)
result = Collections.singleton(Object.class); // least specific info wins
return result.toArray();
}
// --- Misc
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append('{');
if (vtypes != null) {
// Variable types
sb.append("V[");
Iterator i = vtypes.entrySet().iterator();
while (i.hasNext()) {
Map.Entry entry = (Map.Entry)i.next();
sb.append(entry.getKey());
sb.append('=');
Object value = entry.getValue();
if (value != null && value.getClass().isArray())
sb.append(Arrays.asList((Object[])value));
else
sb.append(value);
}
sb.append(']');
}
if (ptypes != null) {
// Parameter types
sb.append("P[");
Iterator i = ptypes.entrySet().iterator();
while (i.hasNext()) {
Map.Entry entry = (Map.Entry)i.next();
sb.append(entry.getKey());
sb.append('=');
Object value = entry.getValue();
if (value != null && value.getClass().isArray())
sb.append(Arrays.asList((Object[])value));
else
sb.append(value);
}
sb.append(']');
}
sb.append('}');
return sb.toString();
}
}