com.bigdata.bop.BOpBase Maven / Gradle / Ivy
/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Aug 16, 2010
*/
package com.bigdata.bop;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* Abstract base class for copy-on-write {@link BOp}s. The {@link BOpBase} class
* is used for query evaluation operators. The copy-on-write contract provides a
* safety margin during concurrent evaluation of query plans by ensuring that
* all references are fully published.
*
* Instances of this class are effectively immutable (mutation APIs always
* return a deep copy of the operator to which the mutation has been applied),
* {@link Serializable} to facilitate distributed computing, and
* {@link Cloneable} to facilitate non-destructive tree rewrites.
*
*
Constructor patterns
*
* {@link BOp}s should define the following public constructors
*
* -
*
public Class(BOp[] args, Map<String,Object> anns)
* - A shallow copy constructor. This is used when initializing a {@link BOp}
* from the caller's data or when generated a query plan from Prolog. There are
* some exceptions to this rule. For example, {@link Constant} does not define a
* shallow copy constructor because that would not provide a means to set the
* constant's value.
* public Class(Class src)
* - A deep copy constructor. Mutation methods make a deep copy of the
* {@link BOp}, apply the mutation to the copy, and then return the copy. This
* is the "effectively immutable" contract. Again, there are some exceptions.
* For example, {@link Var} provides a canonicalized mapping such that reference
* tests may be used to determine if two {@link Var}s are the same. In order to
* support that contract it overrides {@link Var#clone()}.
*
*
* @author Bryan Thompson
* @version $Id$
*/
public class BOpBase extends CoreBaseBOp {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* The argument values - direct access to this field is
* discouraged - the field is protected to support
* mutation APIs and should not be relied on for other purposes.
*
* Note: This field is reported out as a {@link List} so we can make it
* thread safe and, if desired, immutable. However, it is internally a
* simple array. Subclasses can implement mutation operations which return
* deep copies in which the argument values have been modified using
* {@link #_set(int, BOp)}.
*
* If we allowed mutation of the arguments (outside of the object creation
* pattern) then caching of the arguments (or annotations) by classes such
* as EQ will cause {@link #clone()} to fail because (a) it will do
* a field-by-field copy on the concrete implementation class; and (b) it
* will not consistently update the cached references. In order to "fix"
* this problem, any classes which cache arguments or annotations would have
* to explicitly overrides {@link #clone()} in order to set those fields
* based on the arguments on the cloned {@link BOpBase} class.
*
* Note: This must be at least "effectively" final per the effectively
* immutable contract for {@link BOp}s.
*/
private final BOp[] args;
/**
* The operator annotations.
*
* Note: This must be at least "effectively" final per the effectively
* immutable contract for {@link BOp}s.
*/
private final Map annotations;
/**
* Deep copy constructor (required).
*
* Each {@link BOp} MUST implement a public copy constructor with the
* signature:
*
*
* public Foo(Foo)
*
*
* This construct is invoked by {@link #clone()} using reflection and is
* responsible for the deep copy semantics for the {@link BOp}.
*
* The default implementation makes a deep copy of {@link #args()} and
* {@link #annotations()} but DOES NOT perform field-by-field copying.
* Subclasses may simply delegate the constructor to their super class
* unless they have additional fields which need to be copied.
*
* This design pattern was selected because it preserves the immutable
* contract of the {@link BOp} which gives us our thread safety and
* visibility guarantees. Since the deep copy is realized by the {@link BOp}
* implementation classes, it is important that each class take
* responsibility for the deep copy semantics of any fields it may declare.
*
* @param op
* A deep copy will be made of this {@link BOp}.
*
* @throws NullPointerException
* if the argument is null
.
*/
public BOpBase(final BOpBase op) {
// Note: only shallow copy is required to achieve immutable semantics!
if (op.args == BOp.NOARGS || op.args.length == 0) {
// fast path for zero arity operators.
args = BOp.NOARGS;
} else {
args = Arrays.copyOf(op.args, op.args.length);
}
annotations = new LinkedHashMap(op.annotations);
}
/**
* Shallow copy constructor (required).
*
* @param args
* The arguments to the operator.
* @param annotations
* The annotations for the operator (optional).
*/
public BOpBase(final BOp[] args,
final Map annotations) {
if (args == null)
throw new IllegalArgumentException();
checkArgs(args);
this.args = args;
this.annotations = (annotations == null ? new LinkedHashMap(
DEFAULT_INITIAL_CAPACITY)
: annotations);
}
@Override
final public Map annotations() {
return Collections.unmodifiableMap(annotations);
}
@Override
protected boolean annotationsEqual(final BOp o) {
if (o instanceof BOpBase) {
// Fast path when comparing two immutable bops.
return annotationsEqual(annotations, ((BOpBase) o).annotations);
}
return super.annotationsEqual(annotations, o.annotations());
}
/**
* A copy of the args[] array.
*/
final protected BOp[] argsCopy() {
final BOp[] tmp = new BOp[args.length];
for (int i = 0; i < args.length; i++) {
tmp[i] = args[i];
}
return tmp;
}
/**
* A copy of the annotations.
*/
final protected Map annotationsCopy() {
return new LinkedHashMap(annotations);
}
/**
* A reference to the actual annotations map object. This is used in some
* hot spots to avoid creating a new annotations map when we know that the
* annotations will not be modified (annotations are always set within the
* context in which the {@link BOpBase} instance is created so we can know
* this locally by inspection of the code).
*/
final protected Map annotationsRef() {
return annotations;
}
@Override
public BOp get(final int index) {
return args[index];
}
/**
* Set the value of an operand.
*
* Note: This is protected to facilitate copy-on-write patterns. It is not
* public to prevent arbitrary changes to operators outside of methods which
* clone the operator and return the modified version. This is part of the
* effectively immutable contract for {@link BOp}s.
*
* @param index
* The index.
* @param op
* The operand.
*
* @return The old value.
*/
final protected void _set(final int index, final BOp op) {
this.args[index] = op;
}
/**
* Return a new {@link BOpBase} in which the child operand has been replaced
* by the given expression.
*
* @param index
* The index of the child expression to be replaced.
* @param newArg
* The new child expression.
*
* @return A copy of this {@link BOpBase} in which the child operand has
* been replaced.
*/
public BOpBase setArg(final int index, final BOp newArg) {
if (newArg == null)
throw new IllegalArgumentException();
final BOpBase tmp = (BOpBase) this.clone();
tmp._set(index, newArg);
return tmp;
}
/**
* Effectively overwrites the specified argument with the provided value.
* WARNING: this method could break logic of the code, which relies on immutability of the arguments list.
* It is introduced while fixing issues with deferred IV resolution and intended to be used only before IV resolution completed.
* This method triggers mutation notification.
* @see https://jira.blazegraph.com/browse/BLZG-1755 (Date literals in complex FILTER not properly resolved)
* @param index
* The index of the child expression to be replaced.
* @param newArg
* The new child expression.
*/
public void __replaceArg(final int index, final BOp newArg) {
args[index] = newArg;
mutation();
}
@Override
public int arity() {
return args.length;
}
/**
* {@inheritDoc}
*
* Note: This is much less efficient than {@link #argIterator()}.
*/
@Override
final public List args() {
return Collections.unmodifiableList(Arrays.asList(args));
}
/**
* {@inheritDoc}
*
* The iterator does not support removal. (This is more efficient than
* #args()).
*/
@Override
final public Iterator argIterator() {
return new ArgIterator();
}
/**
* An iterator visiting the arguments which does not support removal.
*/
private class ArgIterator implements Iterator {
private int i = 0;
public boolean hasNext() {
return i < args.length;
}
public BOp next() {
if (!hasNext())
throw new NoSuchElementException();
return args[i++];
}
public void remove() {
throw new UnsupportedOperationException();
}
}
// shallow copy
@Override
public BOp[] toArray() {
final BOp[] a = new BOp[args.length];
return Arrays.copyOf(args, args.length, a.getClass());
}
// shallow copy
@SuppressWarnings("unchecked")
public T[] toArray(final T[] a) {
if (a.length < args.length)
return (T[]) Arrays.copyOf(args, args.length, a.getClass());
System.arraycopy(args, 0, a, 0, args.length);
if (a.length > args.length)
a[args.length] = null;
return a;
}
// /**
// * Deep copy of a {@link BOpBase}.
// *
// * @return The deep copy.
// */
// public BOpBase deepCopy() {
//
// final BOpBase bop = (BOpBase) this.clone();
//
// bop.args = deepCopy(bop.args);
//
// bop.annotations = deepCopy(bop.annotations);
//
// return bop;
//
// }
/**
* Deep copy the arguments.
*
* Note: As long as we stick to the immutable semantics for bops, we can
* just make a shallow copy of the arguments in the "copy" constructor and
* then modify them within the specific operator constructor before
* returning control to the caller. This would result in less heap churn.
*/
static protected BOp[] deepCopy(final BOp[] a) {
if (a == BOp.NOARGS || a.length == 0) {
// fast path for zero arity operators.
return BOp.NOARGS;
}
final BOp[] t = new BOp[a.length];
for (int i = 0; i < a.length; i++) {
t[i] = a[i] == null ? null : a[i].clone();
}
return t;
}
/**
* Deep copy the annotations.
*
* Note: This does not know how to deep copy annotations which are not
* {@link BOp}s or immutable objects such as {@link String}s or
* {@link Number}s. Such objects should not be used as annotations.
*
* @todo When attaching large data sets to a query plan they should be
* attached using a light weight reference object which allows them to
* be demanded by a node so deep copy remains a light weight
* operation. This also has the advantage that the objects are
* materialized on a node only when they are needed, which keeps the
* query plan small. Examples would be sending a temporary graph
* containing an ontology or some conditional assertions with a query
* plan.
*/
static protected Map deepCopy(final Map a) {
if (a == BOp.NOANNS) {
// Fast past for immutable, empty annotations.
return a;
}
// allocate map.
final Map t = new LinkedHashMap(a
.size());
// copy map's entries.
final Iterator> itr = a.entrySet().iterator();
while (itr.hasNext()) {
final Map.Entry e = itr.next();
if (e.getValue() instanceof BOp) {
// deep copy bop annotations.
t.put(e.getKey(), ((BOp) e.getValue()).clone());
} else {
// shallow copy anything else.
t.put(e.getKey(), e.getValue());
}
}
// return the copy.
return t;
}
// @SuppressWarnings("unchecked")
// public T getProperty(final String name, final T defaultValue) {
//
// if (!annotations.containsKey(name))
// return defaultValue;
//
// final Object val = annotations.get(name);
//
// if (defaultValue != null && val.getClass() != defaultValue.getClass()) {
//
// /*
// * Attempt to convert to the correct target type.
// */
//
// if (defaultValue.getClass() == Integer.class) {
// return (T) Integer.valueOf("" + val);
// }
// if (defaultValue.getClass() == Long.class) {
// return (T) Long.valueOf("" + val);
// }
// if (defaultValue.getClass() == Float.class) {
// return (T) Float.valueOf("" + val);
// }
// if (defaultValue.getClass() == Double.class) {
// return (T) Double.valueOf("" + val);
// }
//
// }
//
// return (T) val;
//
// }
// @SuppressWarnings("unchecked")
// public T getProperty(final String name) {
//
// return (T) annotations.get(name);
//
// }
@Override
public Object getProperty(final String name) {
return annotations.get(name);
}
// public T getRequiredProperty(final String name) {
//
// @SuppressWarnings("unchecked")
// final T tmp = (T) annotations.get(name);
//
// if (tmp == null)
// throw new IllegalArgumentException("Required property: " + name);
//
// return tmp;
//
// }
// public Object getRequiredProperty(final String name) {
//
// final Object tmp = annotations.get(name);
//
// if (tmp == null)
// throw new IllegalStateException("Required property: " + name
// + " : " + this);
//
// return tmp;
//
// }
/**
* Set an annotation.
*
* Note: This is protected to facilitate copy-on-write patterns. It is not
* public to prevent arbitrary changes to operators outside of methods which
* clone the operator and return the modified version. This is part of the
* effectively immutable contract for {@link BOp}s.
*
* @param name
* The name.
* @param value
* The value.
*
* @return The old value.
*/
protected Object _setProperty(final String name, final Object value) {
return annotations.put(name,value);
}
/**
* Clear an annotation.
*
* Note: This is protected to facilitate copy-on-write patterns. It is not
* public to prevent arbitrary changes to operators outside of methods which
* clone the operator and return the modified version. This is part of the
* effectively immutable contract for {@link BOp}s.
*
* @param name
* The name.
*/
protected void _clearProperty(final String name) {
annotations.remove(name);
}
@Override
public BOpBase setProperty(final String name, final Object value) {
final BOpBase tmp = (BOpBase) this.clone();
tmp._setProperty(name, value);
return tmp;
}
/**
* Conditionally sets the property.
*
* @param name
* The name.
* @param value
* The value.
*
* @return A copy of this {@link BOp} on which the property has been set.
*
* @throws IllegalStateException
* if the property is already set.
*/
public BOpBase setUnboundProperty(final String name, final Object value) {
final BOpBase tmp = (BOpBase) this.clone();
if (tmp._setProperty(name, value) != null)
throw new IllegalStateException("Already set: name=" + name
+ ", value=" + value);
return tmp;
}
/**
* Clear the named annotation.
*
* @param name
* The annotation.
*
* @return A copy of this {@link BOp} in which the named annotation has been
* removed.
*/
public BOpBase clearProperty(final String name) {
if (name == null)
throw new IllegalArgumentException();
final BOpBase tmp = (BOpBase) this.clone();
tmp._clearProperty(name);
return tmp;
}
/**
* Strips off the named annotations.
*
* @param names
* The annotations to be removed.
*
* @return A copy of this {@link BOp} in which the specified annotations do
* not appear.
*/
public BOp clearAnnotations(final String[] names) {
final BOpBase tmp = (BOpBase) this.clone();
for(String name : names) {
tmp._clearProperty(name);
}
return tmp;
}
}