/*******************************************************************************
* 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.data;
import static java.util.Objects.*;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.analog.lyric.collect.ExtendedArrayList;
import com.analog.lyric.dimple.model.core.FactorGraph;
import com.analog.lyric.dimple.model.core.FactorGraphIterables;
import com.analog.lyric.dimple.model.core.IFactorGraphChild;
import com.analog.lyric.dimple.model.core.Ids;
import com.analog.lyric.dimple.model.values.Value;
import com.google.common.collect.UnmodifiableIterator;
/**
* Base implementation for DataLayer abstraction.
* <p>
* @since 0.08
* @author Christopher Barber
*/
public abstract class DataLayerBase<K extends IFactorGraphChild, D extends IDatum>
extends AbstractMap<K, D> implements Cloneable
{
/*-------
* State
*/
private final Class<K> _keyType;
private final Class<D> _baseType;
private final FactorGraph _rootGraph;
/**
* Per-graph data indexed by graph tree index.
*/
private final ExtendedArrayList<FactorGraphData<K,D>> _data;
private final FactorGraphData.Constructor<K,D> _constructor;
protected final int _keyTypeIndex;
private final boolean _createDataOnRead;
/*--------------
* Construction
*/
protected DataLayerBase(
FactorGraph graph,
FactorGraphData.Constructor<K,D> constructor,
Class<K> keyType,
Class<D> baseType)
{
_keyType = keyType;
_keyTypeIndex = Ids.typeIndexForInstanceClass(keyType);
_baseType = baseType;
_rootGraph = graph.getRootGraph();
_data = new ExtendedArrayList<>();
_constructor = constructor;
_createDataOnRead = constructor.createOnRead();
}
protected DataLayerBase(FactorGraph graph, FactorGraphData.Constructor<K,D> constructor)
{
this(graph, constructor, constructor.keyType(), constructor.baseType());
}
protected DataLayerBase(FactorGraph graph, DataDensity density, Class<K> keyType, Class<D> baseType)
{
this(graph, FactorGraphData.constructorForType(density, keyType, baseType));
}
protected DataLayerBase(DataLayerBase<K,D> other)
{
this(other._rootGraph, other._constructor, other._keyType, other._baseType);
for (int i = other._data.size(); --i>=0;)
{
FactorGraphData<K,D> data = other._data.getOrNull(i);
if (data != null)
{
_data.set(i, data.clone(this));
}
}
}
@Override
public abstract DataLayerBase<K,D> clone();
/*----------------
* Object methods
*/
/**
* Compares contents with another object.
* <p>
* This differs from the {@linkplain AbstractMap#equals default implementation} only in the
* case when comparing two empty {@link FactorGraphData} objects, which will only be considered
* equal if they both refer to the same {@link #rootGraph root graph}.
*/
@Override
public boolean equals(@Nullable Object obj)
{
if (this == obj)
{
return true;
}
if (obj instanceof DataLayerBase<?,?>)
{
DataLayerBase<?,?> other = (DataLayerBase<?,?>)obj;
if (_rootGraph != other._rootGraph)
{
return false;
}
for (FactorGraph graph : FactorGraphIterables.subgraphs(_rootGraph))
{
Map<K,D> data = getDataForGraph(graph);
if (data == null)
{
data = Collections.emptyMap();
}
Map<?,?> otherData = other.getDataForGraph(graph);
if (otherData == null)
{
otherData = Collections.emptyMap();
}
if (!data.equals(otherData))
{
return false;
}
}
return true;
}
return super.equals(obj);
}
/*-----------------
* IEquals methods
*/
public boolean objectEquals(@Nullable Object obj)
{
return equals(obj);
}
/*-------------
* Map methods
*/
@Override
public void clear()
{
_data.clear();
}
@Override
public boolean containsKey(@Nullable Object key)
{
final IFactorGraphChild child = _rootGraph.getChild(key);
return _keyType.isInstance(key) && containsDataFor(_keyType.cast(child));
}
@Override
public Set<Map.Entry<K, D>> entrySet()
{
return new EntrySet();
}
@Override
public @Nullable D get(@Nullable Object key)
{
IFactorGraphChild child = _rootGraph.getChild(key);
if (_keyType.isInstance(child))
{
return get(_keyType.cast(child));
}
return null;
}
public @Nullable D get (K var)
{
final FactorGraphData<K,D> data = getDataForChild(var);
return data != null ? data.get(var) : null;
}
@Override
public Set<K> keySet()
{
return new KeySet();
}
@NonNullByDefault(false)
@Override
public @Nullable D put(K var, @Nullable D value)
{
assertSharesRoot(var);
final FactorGraph graph = requireNonNull(var.getParentGraph());
FactorGraphData<K,D> data = _data.getOrNull(graph.getGraphTreeIndex());
if (data != null)
{
return data.put(var, value);
}
else if (value != null)
{
return createDataForGraph(graph).put(var, value);
}
return null;
}
@Override
public @Nullable D remove(@Nullable Object key)
{
IFactorGraphChild child = _rootGraph.getChild(key);
if (_keyType.isInstance(child))
{
return remove(_keyType.cast(child));
}
return null;
}
public @Nullable D remove(K key)
{
final FactorGraphData<K,D> data = getDataForChild(key);
return data != null ? data.remove(key) : null;
}
@Override
public int size()
{
int n = 0;
for (FactorGraphData<?,?> data : _data)
{
if (data != null)
{
n += data.size();
}
}
return n;
}
/*-----------------------
* DataLayerBase methods
*/
/**
* True if layer supports {@link Value} objects.
* <p>
* True if {@link Value} is a subclass or superclass of {@link #baseType()}.
* @since 0.08
*/
public boolean allowsValues()
{
return (_baseType.isAssignableFrom(Value.class)) || Value.class.isAssignableFrom(_baseType);
}
/**
* Base type instance for data held in this layer.
* <p>
* @since 0.08
*/
public final Class<D> baseType()
{
return _baseType;
}
/**
* True if data layer contains a non-null datum for given {@code key}.
* <p>
* This is the same as {@link #containsKey(Object)} but requires an argument with declared key type {@code K}.
* <p>
* @since 0.08
*/
public boolean containsDataFor(K key)
{
final FactorGraphData<K,D> data = getDataForChild(key);
return data != null && data.containsKey(key);
}
/**
* Force instantiation of {@link FactorGraphData} for given {@code graph} for this layer.
* <p>
* Unlike {@link #getDataForGraph(FactorGraph)} this will create a new instance if necessary.
* <p>
* @param graph must be a graph in the same graph tree as this layer.
* @since 0.08
*/
public FactorGraphData<K,D> createDataForGraph(FactorGraph graph)
{
assertSharesRoot(graph);
FactorGraphData<K,D> data = _data.getOrNull(graph.getGraphTreeIndex());
if (data == null)
{
FactorGraphData<K,D> newData = _constructor.apply(this, graph);
setDataForGraph(newData);
data = newData;
}
return data;
}
/**
* The {@linkplain FactorGraphData.Constructor constructor object} used to create new {@link FactorGraphData}
* instances.
* <p>
* This is used by {@link #createDataForGraph(FactorGraph)} to create new instances.
* <p>
* @since 0.08
*/
public final FactorGraphData.Constructor<K,D> dataConstructor()
{
return _constructor;
}
/**
* Lookup data for child with given graph tree id.
* @since 0.08
* @see #get(Object)
*/
public @Nullable D getByGraphTreeId(long id)
{
int localId = Ids.localIdFromGraphTreeId(id);
int graphTreeIndex = Ids.graphTreeIndexFromGraphTreeId(id);
if (Ids.typeIndexFromLocalId(localId) == _keyTypeIndex)
{
return getByGraphTreeAndLocalIndices(graphTreeIndex, Ids.indexFromLocalId(localId));
}
return null;
}
/**
* Lookup data for key with given graph tree and local indices.
* @since 0.08
* @see #getByGraphTreeId(long)
*/
public @Nullable D getByGraphTreeAndLocalIndices(int graphTreeIndex, int localIndex)
{
FactorGraphData<K,D> data = _data.getOrNull(graphTreeIndex);
return data != null ? data.getByLocalIndex(localIndex) : null;
}
public @Nullable FactorGraphData<K,D> getDataForGraph(FactorGraph graph)
{
FactorGraphData<K,D> data = null;
if (sharesRoot(graph))
{
data = _data.getOrNull(graph.getGraphTreeIndex());
if (data == null && _createDataOnRead)
{
FactorGraphData<K,D> newData = _constructor.apply(this, graph);
setDataForGraph(newData);
data = newData;
}
}
return data;
}
public Iterable<? extends FactorGraphData<K,D>> getData()
{
return new Iterable<FactorGraphData<K,D>>() {
@Override
public Iterator<FactorGraphData<K, D>> iterator()
{
return new DataIterator();
}
};
}
public Class<K> keyType()
{
return _keyType;
}
/**
* If true, this is a view of data held in other objects.
* <p>
* When true, cloning this object will not result in a distinct copy of the data.
* <p>
* The default implementation returns false.
* @since 0.08
*/
public boolean isView()
{
return false;
}
public @Nullable FactorGraphData<K,D> removeDataForGraph(FactorGraph graph)
{
return sharesRoot(graph) ? _data.set(graph.getGraphTreeIndex(), null) : null;
}
/**
* Root {@link FactorGraph} of the graph tree represented by this data layer.
* @since 0.08
*/
public FactorGraph rootGraph()
{
return _rootGraph;
}
public @Nullable FactorGraphData<K,D> setDataForGraph(FactorGraphData<K,D> data)
{
if (data.layer() != this)
{
throw new IllegalArgumentException(String.format("Data belongs to a different layer"));
}
return _data.set(data.graph().getGraphTreeIndex(), data);
}
/**
* True if {@code child} is in the same graph tree represented by this data layer.
* @since 0.08
*/
public boolean sharesRoot(IFactorGraphChild child)
{
return child.getRootGraph() == _rootGraph;
}
/*--------------------------
* Non-public inner classes
*/
private class DataIterator extends UnmodifiableIterator<FactorGraphData<K,D>>
{
private int _graphTreeIndex;
private final int _maxGraphTreeIndex;
private @Nullable FactorGraphData<K,D> _next;
DataIterator()
{
_graphTreeIndex = -1;
_maxGraphTreeIndex = _rootGraph.getMaxGraphTreeIndex();
if (_data.size() <= _maxGraphTreeIndex)
{
_data.setSize(_maxGraphTreeIndex + 1);
}
}
@Override
public boolean hasNext()
{
return advance() != null;
}
@Override
public @Nullable FactorGraphData<K, D> next()
{
FactorGraphData<K,D> data = advance();
_next = null;
return data;
}
private @Nullable FactorGraphData<K,D> advance()
{
FactorGraphData<K,D> data = _next;
if (data == null)
{
while (++_graphTreeIndex <= _maxGraphTreeIndex)
{
data = _data.get(_graphTreeIndex);
if (data != null)
{
break;
}
if (_createDataOnRead)
{
FactorGraph graph = _rootGraph.getGraphByTreeIndex(_graphTreeIndex);
if (graph != null)
{
data = createDataForGraph(graph);
break;
}
}
}
_next = data;
}
return data;
}
}
private class KeyIter extends UnmodifiableIterator<K>
{
private Iterator<FactorGraphData<K,D>> _dataIter = new DataIterator();
private Iterator<K> _iter = Collections.emptyIterator();
@Override
public boolean hasNext()
{
while (!_iter.hasNext())
{
if (_dataIter.hasNext())
{
_iter = _dataIter.next().keySet().iterator();
}
else
{
return false;
}
}
return true;
}
@Override
public @Nullable K next()
{
hasNext();
return _iter.next();
}
}
private class KeySet extends AbstractSet<K>
{
@Override
public void clear()
{
DataLayerBase.this.clear();
}
@Override
public boolean contains(@Nullable Object obj)
{
return DataLayerBase.this.containsKey(obj);
}
@Override
public Iterator<K> iterator()
{
return new KeyIter();
}
@Override
public boolean remove(@Nullable Object obj)
{
return DataLayerBase.this.remove(obj) != null;
}
@Override
public int size()
{
return DataLayerBase.this.size();
}
}
private class EntryIter extends UnmodifiableIterator<Map.Entry<K,D>>
{
private Iterator<FactorGraphData<K,D>> _dataIter = new DataIterator();
private Iterator<Map.Entry<K,D>> _iter = Collections.emptyIterator();
@Override
public boolean hasNext()
{
while (!_iter.hasNext())
{
if (_dataIter.hasNext())
{
_iter = _dataIter.next().entrySet().iterator();
}
else
{
return false;
}
}
return true;
}
@Override
public @Nullable Map.Entry<K, D> next()
{
hasNext();
return _iter.next();
}
}
private class EntrySet extends AbstractSet<Map.Entry<K, D>>
{
@NonNullByDefault(false)
@Override
public boolean add(Map.Entry<K, D> entry)
{
final K key = entry.getKey();
final D value = entry.getValue();
return !Objects.equals(value, DataLayerBase.this.put(key, value));
}
@Override
public void clear()
{
DataLayerBase.this.clear();
}
@Override
public boolean contains(@Nullable Object obj)
{
if (obj instanceof Map.Entry)
{
Map.Entry<?,?> entry = (Map.Entry<?,?>)obj;
return Objects.equals(DataLayerBase.this.get(entry.getKey()), entry.getValue());
}
return false;
}
@Override
public Iterator<Map.Entry<K, D>> iterator()
{
return new EntryIter();
}
@Override
public boolean remove(@Nullable Object obj)
{
if (obj instanceof Map.Entry)
{
Map.Entry<?,?> entry = (Map.Entry<?,?>)obj;
IDatum value = DataLayerBase.this.get(entry.getKey());
if (Objects.equals(value, entry.getValue()))
{
DataLayerBase.this.remove(entry.getKey());
return true;
}
}
return false;
}
@Override
public int size()
{
return DataLayerBase.this.size();
}
}
/*--------------------
* Non-public methods
*/
void assertSharesRoot(IFactorGraphChild child)
{
if (!sharesRoot(child))
{
throw new IllegalArgumentException(String.format("%s does not share root graph with %s", child, this));
}
}
private @Nullable FactorGraphData<K,D> getDataForChild(IFactorGraphChild key)
{
FactorGraph graph = key.getParentGraph();
return graph != null ? getDataForGraph(graph) : null;
}
}