/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.jena.sparql.engine.binding;
import java.util.Iterator ;
import java.util.Objects;
import org.apache.jena.atlas.iterator.IteratorConcat ;
import org.apache.jena.graph.Node ;
import org.apache.jena.sparql.core.Var ;
import org.apache.jena.sparql.util.FmtUtils ;
/** Machinary encapsulating a mapping from a name to a value.
* The "parent" is a shared, immutable, common set of bindings.
* An association of var/node must not override a setting in the parent.
*
* @see BindingFactory
* @see BindingMap for mutable bindings.
*/
abstract public class BindingBase implements Binding
{
static boolean CHECKING = true ;
static boolean UNIQUE_NAMES_CHECK = true ;
// This is a set of bindings, each binding being one pair (var, value).
protected Binding parent ;
// Tracking children is for flexiblity.
// It is not needed for flatten results sets (i.e. with nulls in)
// but is needed for nested result set that record subqueries.
// and have nested results.
// But keeping the child reference means that used bindings are not freed
// to the GC until the parent is freed and hence the root is finished with -
// which is all results.
// private List children = new ArrayList() ;
// protected void addChild(Binding child) { children.add(child) ; }
// private Iterator getChildren() { return children.listIterator() ; }
protected BindingBase(Binding _parent)
{
parent = _parent ;
//parent.addChild((BindingBase)this) ;
}
public Binding getParent() { return parent ; }
/** Iterate over all the names of variables. */
@Override
final public Iterator<Var> vars()
{
// Hidesight - replace with accumulator style vars1(accumulator)
Iterator<Var> iter = vars1() ;
if ( parent != null )
iter = IteratorConcat.concat(parent.vars(), iter ) ;
return iter ;
}
protected abstract Iterator<Var> vars1() ;
@Override
final public int size()
{
int x = size1() ;
if ( parent != null )
x = x + parent.size() ;
return x ;
}
protected abstract int size1() ;
@Override
public boolean isEmpty()
{
if ( ! isEmpty1() )
return false ;
if ( parent == null )
return true ;
return parent.isEmpty() ;
}
protected abstract boolean isEmpty1() ;
/** Test whether a name is bound to some object */
@Override
public boolean contains(Var var)
{
if ( contains1(var) )
return true ;
if ( parent == null )
return false ;
return parent.contains(var) ;
}
protected abstract boolean contains1(Var var) ;
/** Return the object bound to a name, or null */
@Override
final public Node get(Var var)
{
Node node = get1(var) ;
if ( node != null )
return node ;
if ( parent == null )
return null ;
return parent.get(var) ;
}
protected abstract Node get1(Var var) ;
@Override
public String toString()
{
StringBuffer sbuff = new StringBuffer() ;
format1(sbuff) ;
if ( parent != null )
{
String tmp = parent.toString() ;
if ( tmp != null && (tmp.length() != 0 ) )
{
sbuff.append(" -> ") ;
sbuff.append(tmp) ;
}
}
return sbuff.toString() ;
}
// Do one level of binding
public void format1(StringBuffer sbuff)
{
String sep = "" ;
for ( Iterator<Var> iter = vars1() ; iter.hasNext() ; )
{
Object obj = iter.next() ;
Var var = (Var)obj ;
sbuff.append(sep) ;
sep = " " ;
format(sbuff, var) ;
}
}
protected void format(StringBuffer sbuff, Var var)
{
Node node = get(var) ;
String tmp = FmtUtils.stringForObject(node) ;
sbuff.append("( ?"+var.getVarName()+" = "+tmp+" )") ;
}
// Do one level of binding
public String toString1()
{
StringBuffer sbuff = new StringBuffer() ;
format1(sbuff) ;
return sbuff.toString() ;
}
@Override
public int hashCode() { return hashCode(this) ; }
@Override
public boolean equals(Object other)
{
if ( this == other ) return true ;
if ( ! ( other instanceof Binding) ) return false ;
Binding binding = (Binding)other ;
return equals(this, binding) ;
}
// Not everything derives from BindingBase.
public static int hashCode(Binding bind)
{
int hash = 0xC0 ;
for ( Iterator<Var> iter = bind.vars() ; iter.hasNext() ; )
{
Var var = iter.next() ;
Node node = bind.get(var) ;
hash ^= var.hashCode() ;
hash ^= node.hashCode() ;
}
return hash ;
}
public static boolean equals(Binding bind1, Binding bind2)
{
if ( bind1 == bind2 ) return true ;
// Same variables?
if ( bind1.size() != bind2.size() )
return false ;
for ( Iterator<Var> iter1 = bind1.vars() ; iter1.hasNext() ; )
{
Var var = iter1.next() ;
Node node1 = bind1.get(var) ;
Node node2 = bind2.get(var) ;
if ( ! Objects.equals(node1, node2) )
return false ;
}
// No need to check the other way round as the sizes matched.
return true ;
}
}