/*******************************************************************************
* Copyright 2015 Analog Devices, Inc.
*
* 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 com.analog.lyric.dimple.model.variables;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.RandomAccess;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.analog.lyric.dimple.events.IModelEventSource;
import com.analog.lyric.dimple.model.core.FactorGraph;
import com.analog.lyric.dimple.model.core.FactorGraphChild;
import com.analog.lyric.dimple.model.core.Ids;
import com.analog.lyric.util.misc.Internal;
import com.google.common.primitives.Longs;
import cern.colt.map.OpenLongObjectHashMap;
import net.jcip.annotations.ThreadSafe;
/**
* Represents a block of {@link Variable}s in a {@link FactorGraph}.
* <p>
* The list of variables in this block is immutable and cannot change once it has been created.
* <p>
* Note that while the list of variables cannot change, the underlying representation can change if
* the {@linkplain FactorGraph#reindexGraphTree graph tree is reindexed}. This may change the value
* of the {@link #hashCode()}, so avoid reindexing when you have {@link java.util.HashSet HashSet}
* containing blocks from the graph tree, or a {@link java.util.HashMap HashMap} with blocks used
* as keys.
* <p>
* @since 0.08
* @author Christopher Barber
* @see FactorGraph#addVariableBlock(Collection)
*/
@ThreadSafe
public final class VariableBlock extends FactorGraphChild implements List<Variable>, RandomAccess
{
/*-------
* State
*/
private final long[] _variableGraphTreeIds;
private int _hashCode;
/*--------------
* Construction
*/
/**
* Construct a new variable block.
* <p>
* This constructor is intended to be used internally in the implementation of
* {@link FactorGraph#addVariableBlock(Collection)}.
* <p>
* @param parent is the graph
* @param variables are the variables that will comprise the block. The variables will be added in the
* order of the {@linkplain Collection#iterator iterator}.
* @since 0.08
* @throws IllegalArgumentException if a variable does not belong to the same tree of graphs as {@code parent}
* or the same variable appears more than once.
* @category internal
*/
@Internal
public VariableBlock(FactorGraph parent, Collection<Variable> variables)
{
super();
super.setParentGraph(parent);
final int n = variables.size();
if (n <= 0)
{
throw new IllegalArgumentException("Cannot create empty VariableBlock");
}
final OpenLongObjectHashMap varSet = new OpenLongObjectHashMap(n);
final FactorGraph root = parent.getRootGraph();
_variableGraphTreeIds = new long[n];
int i = -1;
for (Variable var : variables)
{
if (var.getRootGraph() != root)
{
throw new IllegalArgumentException(String.format("Variable '%s' not in graph tree", var));
}
// TODO - perhaps boundary variables should be stored using their boundary id
final long id = var.getGraphTreeId();
if (!varSet.put(id, null))
{
throw new IllegalArgumentException(String.format("Variable '%s' was specified more than once", var));
}
_variableGraphTreeIds[++i] = id;
}
_hashCode = Arrays.hashCode(_variableGraphTreeIds);
}
/*----------------
* Object methods
*/
@Override
public boolean equals(@Nullable Object obj)
{
if (obj == this)
{
return true;
}
if (obj instanceof VariableBlock && obj.hashCode() == _hashCode)
{
return Arrays.equals(_variableGraphTreeIds, ((VariableBlock)obj)._variableGraphTreeIds);
}
return false;
}
@Override
public int hashCode()
{
return _hashCode;
}
/*----------------------------
* IDimpleEventSource methods
*/
@Override
public String getEventSourceName()
{
return toString();
}
@Override
public @Nullable IModelEventSource getModelEventSource()
{
return getParentGraph();
}
@Override
public void notifyListenerChanged()
{
}
/*--------------------------
* FactorGraphChild methods
*/
@SuppressWarnings("null")
@Override
public FactorGraph getParentGraph()
{
return _parentGraph;
}
@Override
protected void fixGraphTreeIndices(int[] old2newGraphTreeIndex)
{
final long[] ids = _variableGraphTreeIds;
for (int i = ids.length; --i>=0;)
{
final long id = ids[i];
final int oldGraphTreeIndex = Ids.graphTreeIndexFromGraphTreeId(id);
final int newGraphTreeIndex = old2newGraphTreeIndex[oldGraphTreeIndex];
ids[i] = Ids.graphTreeIdFromParts(newGraphTreeIndex, Ids.localIdFromGraphTreeId(id));
}
_hashCode = Arrays.hashCode(ids);
}
@Override
protected void fixVarIndicesForGraph(int graphTreeIndex, int[] old2newVarIndex)
{
final long[] ids = _variableGraphTreeIds;
for (int i = ids.length; --i>=0;)
{
final long id = ids[i];
if (graphTreeIndex == Ids.graphTreeIndexFromGraphTreeId(id))
{
final int oldVarIndex = Ids.indexFromLocalId(Ids.localIdFromGraphTreeId(id));
final int newVarIndex = old2newVarIndex[oldVarIndex];
ids[i] = Ids.graphTreeIdFromParts(graphTreeIndex, Ids.localIdFromParts(Ids.VARIABLE_TYPE, newVarIndex));
}
}
_hashCode = Arrays.hashCode(ids);
}
/*---------------
* List methods
*/
@Override
public boolean isEmpty()
{
return false; // Empty variable block is not allowed, see constructor.
}
@Override
public int size()
{
return _variableGraphTreeIds.length;
}
@NonNullByDefault(false)
@Override
public boolean contains(Object obj)
{
return indexOf(obj) >= 0;
}
@NonNullByDefault(false)
@Override
public boolean containsAll(Collection<?> collection)
{
for (Object obj : collection)
{
if (!contains(obj))
{
return false;
}
}
return true;
}
@Override
public Variable get(int index)
{
final long id = _variableGraphTreeIds[index];
Variable var = (Variable)requireParentGraph().getNodeByGraphTreeId(id);
if (var == null)
{
throw new IllegalStateException(String.format("Variable with graph tree id 0x%X no longer in graph", id));
}
return var;
}
@NonNullByDefault(false)
@Override
public int indexOf(Object obj)
{
if (obj instanceof Variable)
{
Variable var = (Variable)obj;
if (var.getRootGraph() == requireParentGraph().getRootGraph())
{
return Longs.indexOf(_variableGraphTreeIds, var.getGraphTreeId());
}
}
return -1;
}
@Override
public Iterator<Variable> iterator()
{
return new Wrapper().iterator();
}
@NonNullByDefault(false)
@Override
public int lastIndexOf(Object obj)
{
if (obj instanceof Variable)
{
Variable var = (Variable)obj;
if (var.getRootGraph() == requireParentGraph().getRootGraph())
{
return Longs.lastIndexOf(_variableGraphTreeIds, var.getGraphTreeId());
}
}
return -1;
}
@Override
public ListIterator<Variable> listIterator()
{
return listIterator(0);
}
@Override
public ListIterator<Variable> listIterator(int index)
{
return new Wrapper().listIterator(index);
}
@Override
public List<Variable> subList(int fromIndex, int toIndex)
{
return new Wrapper().subList(fromIndex, toIndex);
}
@Override
public Object[] toArray()
{
return new Wrapper().toArray();
}
@SuppressWarnings("unchecked")
@NonNullByDefault(false)
@Override
public <T> T[] toArray(T[] array)
{
return new Wrapper().toArray(array);
}
/*-----------------------
* VariableBlock methods
*/
/**
* Get the {@linkplain Variable#getGraphTreeId() graph tree id} of a variable in the block.
* <p>
* Because this is the underlying representation, this is faster than calling
* <blockquote>
* {@code get(index).getGraphTreeId()}.
* </blockquote>
* <p>
* @param index is a non-negative value less than {@link #size}.
* @since 0.08
* @throws IndexOutOfBoundsException if index is not in valid range.
*/
public long getVariableGraphTreeId(int index)
{
return _variableGraphTreeIds[index];
}
/*--------------------------
* Unsupported list methods
*/
@NonNullByDefault(false)
@Override
public boolean add(Variable e)
{
throw immutable();
}
@NonNullByDefault(false)
@Override
public void add(int index, Variable element)
{
throw immutable();
}
@NonNullByDefault(false)
@Override
public boolean addAll(Collection<? extends Variable> c)
{
throw immutable();
}
@NonNullByDefault(false)
@Override
public boolean addAll(int index, Collection<? extends Variable> c)
{
throw immutable();
}
@Override
public void clear()
{
throw immutable();
}
@NonNullByDefault(false)
@Override
public boolean remove(Object o)
{
throw immutable();
}
@Override
public Variable remove(int index)
{
throw immutable();
}
@NonNullByDefault(false)
@Override
public boolean removeAll(Collection<?> c)
{
throw immutable();
}
@NonNullByDefault(false)
@Override
public boolean retainAll(Collection<?> c)
{
throw immutable();
}
@NonNullByDefault(false)
@Override
public Variable set(int index, Variable element)
{
throw immutable();
}
/*-----------------
* Private methods
*/
/**
* Simple wrapper for this class that allows us to use AbstractList implementations
* for some of the non-trivial methods.
*
* @since 0.08
* @author Christopher Barber
*/
private class Wrapper extends AbstractList<Variable>
{
@Override
public Variable get(int index)
{
return VariableBlock.this.get(index);
}
@Override
public int size()
{
return _variableGraphTreeIds.length;
}
}
private static RuntimeException immutable()
{
return new UnsupportedOperationException("VariableBlock is immutable");
}
}