352 lines
12 KiB
C#
352 lines
12 KiB
C#
using ln.logging;
|
|
using ln.objects.index;
|
|
using ln.objects.serialization;
|
|
using ln.objects.storage;
|
|
using ln.type;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.Serialization;
|
|
using ln.objects.serialization.binary;
|
|
using ln.collections;
|
|
using System;
|
|
|
|
namespace ln.objects
|
|
{
|
|
public class ObjectStore : IDisposable
|
|
{
|
|
public static bool DEBUG;
|
|
|
|
public static Guid TYPECACHE_GUID = new Guid("00000000-0000-0000-0001-000000000001");
|
|
public static Guid SPECIAL_GUID_LIMIT = new Guid("00000000-0000-0000-0002-000000000000");
|
|
|
|
public String FileName => objectFile.FileName;
|
|
|
|
public Factory<Serializer, ObjectStore> SerializerFactory { get; }
|
|
public Factory<Deserializer, ObjectStore> DeserializerFactory { get; }
|
|
|
|
Deserializer defaultDeserializer;
|
|
|
|
BinaryObjectFile objectFile;
|
|
ObjectCache objectCache;
|
|
BTreeValueSet<Type, Guid> objectTypeCache;
|
|
|
|
Dictionary<Type, IndexLeaf> indeces = new Dictionary<Type, IndexLeaf>();
|
|
|
|
|
|
SaveTransaction currentTransaction;
|
|
|
|
public ObjectStore(String filename)
|
|
:this(filename,() => new BinarySerializer(),() => new BinaryDeserializer())
|
|
{
|
|
}
|
|
public ObjectStore(string filename, Factory<Serializer> serializerFactory, Factory<Deserializer> deserializerFactory)
|
|
: this(filename, (o) => serializerFactory(), (o) => deserializerFactory())
|
|
{
|
|
}
|
|
public ObjectStore(string filename, Factory<Serializer, ObjectStore> serializerFactory, Factory<Deserializer, ObjectStore> deserializerFactory)
|
|
{
|
|
SerializerFactory = serializerFactory;
|
|
DeserializerFactory = deserializerFactory;
|
|
|
|
objectFile = new BinaryObjectFile(filename);
|
|
objectCache = new ObjectCache();
|
|
}
|
|
|
|
public void Open()
|
|
{
|
|
lock (this)
|
|
{
|
|
objectFile.Open();
|
|
|
|
defaultDeserializer = DeserializerFactory(this);
|
|
defaultDeserializer.OnLookupObjectByReference += LookupObjectByReference;
|
|
|
|
if (objectFile.TryReadBinaryObject(TYPECACHE_GUID, out byte[] typeCacheBytes))
|
|
{
|
|
object otc = null;
|
|
if (defaultDeserializer.DeserializeObject(typeCacheBytes, ref otc))
|
|
{
|
|
KeyValuePair<String, Guid>[] typeCacheValues = (KeyValuePair<String, Guid>[])otc;
|
|
objectTypeCache = new BTreeValueSet<Type, Guid>();
|
|
objectTypeCache.AddRange(typeCacheValues.Select((kvp) => new KeyValuePair<Type, Guid>(Type.GetType(kvp.Key), kvp.Value)));
|
|
|
|
objectFile.RemoveBinaryObjects(TYPECACHE_GUID);
|
|
objectFile.Flush();
|
|
}
|
|
else
|
|
{
|
|
objectFile.RemoveBinaryObjects(TYPECACHE_GUID);
|
|
objectFile.Flush();
|
|
|
|
objectTypeCache = new BTreeValueSet<Type, Guid>();
|
|
|
|
foreach (Guid uid in objectFile.ObjectUIDs)
|
|
{
|
|
if (objectFile.TryReadBinaryObject(uid, out byte[] serializedBytes))
|
|
{
|
|
if (defaultDeserializer.TryGetType(serializedBytes, out Type type))
|
|
{
|
|
objectTypeCache.TryAdd(type, uid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
objectTypeCache = new BTreeValueSet<Type, Guid>();
|
|
}
|
|
}
|
|
}
|
|
public void Close()
|
|
{
|
|
lock (this)
|
|
{
|
|
if (objectFile != null)
|
|
{
|
|
KeyValuePair<String, Guid>[] typeCacheValues = objectTypeCache.GetKeyValuePairs().Select((kvp) => new KeyValuePair<string, Guid>(kvp.Key.GetSimpleQualifiedName(), kvp.Value)).ToArray();
|
|
|
|
if (SerializerFactory(this).SerializeObject(typeCacheValues, out byte[] typeCacheBytes))
|
|
{
|
|
objectFile.WriteBinaryObject(TYPECACHE_GUID, typeCacheBytes);
|
|
objectFile.Flush();
|
|
}
|
|
|
|
objectFile.Dispose();
|
|
objectFile = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public T LoadObject<T>(Guid uid) => (T)LoadObject(uid, typeof(T));
|
|
public object LoadObject(Guid uid,Type type)
|
|
{
|
|
lock (this)
|
|
{
|
|
if (!objectCache.TryGetObject(uid,out object o))
|
|
{
|
|
o = Activator.CreateInstance(type, true);
|
|
objectCache.Add(uid, o);
|
|
|
|
byte[] boData = objectFile.ReadBinaryObject(uid);
|
|
|
|
if (!defaultDeserializer.DeserializeObject(boData, ref o))
|
|
throw new Exception("unable to deserialize");
|
|
}
|
|
return o;
|
|
}
|
|
}
|
|
|
|
public IEnumerable<object> LoadObjects(Type type)
|
|
{
|
|
foreach (Guid uid in objectTypeCache[type])
|
|
{
|
|
yield return LoadObject(uid, type);
|
|
}
|
|
}
|
|
public IEnumerable<T> LoadObjects<T>()
|
|
{
|
|
foreach (Guid uid in objectTypeCache[typeof(T)])
|
|
{
|
|
yield return LoadObject<T>(uid);
|
|
}
|
|
}
|
|
|
|
|
|
private bool LookupObjectByReference(object reference, Type targetType, out object o)
|
|
{
|
|
if (DEBUG)
|
|
Logging.Log(LogLevel.DEBUG, "LookupObjectReference: {0} [ {1} ]", reference, targetType.Name);
|
|
|
|
o = LoadObject((Guid)reference, targetType);
|
|
return true;
|
|
}
|
|
|
|
private void TransactionalAction(SaveTransaction saveTransaction, Action action)
|
|
{
|
|
lock (this)
|
|
{
|
|
SaveTransaction oldTransaction = currentTransaction;
|
|
currentTransaction = saveTransaction;
|
|
|
|
try
|
|
{
|
|
action();
|
|
}
|
|
finally
|
|
{
|
|
currentTransaction = oldTransaction;
|
|
}
|
|
}
|
|
}
|
|
|
|
public Guid SaveObject(object o) => SaveObject(GetObjectUID(o), o);
|
|
public Guid SaveObject(Guid uid, object o)
|
|
{
|
|
lock (this)
|
|
{
|
|
SaveTransaction saveTransaction = new SaveTransaction(this);
|
|
|
|
TransactionalAction(saveTransaction, () => PrepareSave(saveTransaction, uid, o));
|
|
|
|
foreach (KeyValuePair<Guid, byte[]> bo in saveTransaction.BinaryObjects)
|
|
if (bo.Value != null)
|
|
{
|
|
if (DEBUG)
|
|
Logging.Log(LogLevel.DEBUG, "Storing {0} [ {1} ]", bo.Key, objectCache[bo.Key].GetType());
|
|
objectFile.WriteBinaryObject(bo.Key, bo.Value);
|
|
}
|
|
else if (DEBUG)
|
|
Logging.Log(LogLevel.DEBUG, "Ignoring unchanged {0} [ {1} ]", bo.Key, objectCache[bo.Key].GetType());
|
|
|
|
return uid;
|
|
}
|
|
}
|
|
|
|
private void PrepareSave(SaveTransaction saveTransaction,Guid uid, object o)
|
|
{
|
|
saveTransaction.PushReferencedObjects();
|
|
|
|
if (!saveTransaction.Serializer.SerializeObject(o, out byte[] serializedBytes))
|
|
throw new SerializationException();
|
|
|
|
if (!Object.ReferenceEquals(null, o))
|
|
{
|
|
objectTypeCache.TryAdd(o.GetType(), uid);
|
|
GetIndexLeaf(o.GetType()).Reindex(uid, o);
|
|
}
|
|
|
|
if (objectFile.TryReadBinaryObject(uid, out byte[] storedBytes) && storedBytes.AreEqual(serializedBytes))
|
|
{
|
|
saveTransaction.BinaryObjects.Add(uid, null);
|
|
saveTransaction.PopReferencedObjects();
|
|
}
|
|
else
|
|
{
|
|
saveTransaction.BinaryObjects.Add(uid, serializedBytes);
|
|
saveTransaction.PopReferencedObjectsToFinal();
|
|
}
|
|
|
|
foreach (object ro in saveTransaction.ReferencedObjects.ToArray())
|
|
{
|
|
Guid oUid = GetObjectUID(ro);
|
|
if (!saveTransaction.BinaryObjects.ContainsKey(oUid))
|
|
PrepareSave(saveTransaction, oUid, ro);
|
|
}
|
|
}
|
|
|
|
public Guid GetObjectUID(object o)
|
|
{
|
|
lock (this)
|
|
{
|
|
if (!objectCache.TryGetUID(o, out Guid uid))
|
|
{
|
|
uid = Guid.NewGuid();
|
|
objectCache.Add(uid, o);
|
|
}
|
|
return uid;
|
|
}
|
|
}
|
|
|
|
private IndexLeaf GetIndexLeaf(Type type)
|
|
{
|
|
if (!indeces.TryGetValue(type,out IndexLeaf indexLeaf))
|
|
{
|
|
indexLeaf = new IndexLeaf(type, null, (o) => o);
|
|
indeces.Add(type, indexLeaf);
|
|
}
|
|
return indexLeaf;
|
|
}
|
|
|
|
public bool EnsureIndeces(Type type,IEnumerable<KeyValuePair<string,index.Index>> indexDefinitions)
|
|
{
|
|
bool rebuild = false;
|
|
|
|
foreach (KeyValuePair<string, index.Index> indexDefinition in indexDefinitions)
|
|
rebuild |= EnsureIndex(type, indexDefinition.Key, indexDefinition.Value, false);
|
|
|
|
if (rebuild)
|
|
RebuildIndeces();
|
|
|
|
return rebuild;
|
|
}
|
|
|
|
public IEnumerable<object> QueryObjects(Type type, string path, Func<object, bool> criterion) => QueryUids(type, path, criterion).Select((uid) => LoadObject(uid, type));
|
|
public IEnumerable<Guid> QueryUids(Type type,string path,Func<object,bool> criterion)
|
|
{
|
|
HashSet<Guid> result = new HashSet<Guid>();
|
|
GetIndexLeaf(type).GetLeaf(path).Match(criterion, result);
|
|
return result;
|
|
}
|
|
|
|
public bool EnsureIndex(Type type, string path, index.Index index) => EnsureIndex(type, path, index, true);
|
|
private bool EnsureIndex(Type type, string path, index.Index index, bool rebuild)
|
|
{
|
|
IndexLeaf indexLeaf = GetIndexLeaf(type).GetLeaf(path);
|
|
if (index.GetType().Equals(indexLeaf.Index))
|
|
return false;
|
|
|
|
indexLeaf.Index = index;
|
|
|
|
if (rebuild)
|
|
RebuildIndeces();
|
|
|
|
return true;
|
|
}
|
|
|
|
private void RebuildIndeces()
|
|
{
|
|
foreach (Type type in objectTypeCache.Keys)
|
|
{
|
|
IndexLeaf indexLeaf = GetIndexLeaf(type);
|
|
indexLeaf.Clear();
|
|
foreach (object value in LoadObjects(type))
|
|
indexLeaf.Reindex(GetObjectUID(value), value);
|
|
}
|
|
}
|
|
|
|
|
|
public void Dispose()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
class SaveTransaction
|
|
{
|
|
public ObjectStore ObjectStore { get; }
|
|
public Serializer Serializer { get; }
|
|
|
|
public HashSet<object> ReferencedObjects { get; } = new HashSet<object>();
|
|
|
|
public Stack<HashSet<object>> referencedObjectsStack { get; } = new Stack<HashSet<object>>();
|
|
HashSet<object> currentReferencedObjectsSet => referencedObjectsStack.Peek();
|
|
|
|
public Dictionary<Guid, byte[]> BinaryObjects { get; } = new Dictionary<Guid, byte[]>();
|
|
|
|
public SaveTransaction(ObjectStore objectStore)
|
|
{
|
|
ObjectStore = objectStore;
|
|
Serializer = ObjectStore.SerializerFactory(objectStore);
|
|
Serializer.OnLookupReference += LookupReference;
|
|
}
|
|
|
|
public bool LookupReference(object value, out object reference)
|
|
{
|
|
reference = ObjectStore.GetObjectUID(value);
|
|
currentReferencedObjectsSet.Add(value);
|
|
return true;
|
|
}
|
|
|
|
public void PushReferencedObjects() => referencedObjectsStack.Push(new HashSet<object>());
|
|
public void PopReferencedObjects() => referencedObjectsStack.Pop();
|
|
public void PopReferencedObjectsToFinal()
|
|
{
|
|
HashSet<object> ro = referencedObjectsStack.Pop();
|
|
foreach (object o in ro)
|
|
ReferencedObjects.Add(o);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|