using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using TradeIdeas.MiscSupport;
using TradeIdeas.XML;
using System.Xml;
using System.Collections;
using System.Threading;
using System.Reflection;
namespace TradeIdeas.TIProData
{
///
/// A DataNode is a base class for presenting streaming data. It's a little like pub-sub,
/// but DataNodes are easy to stack. If you want to "subscribe" in this system, you need
/// to Find() a DataNode. While there are no restrictions on how that DataNode is
/// implemented, it's a safe bet that the DataNode you found will call Find() on other
/// DataNodes, forming a tree. (More precisely, a DAG. One DataNode can find two others,
/// each of which share a fourth DataNode.)
///
/// There are four basic operations related to DataNodes.
/// 1) You can Find() a data node. Find() might create the DataNode from scratch. If
/// it's at all possible, Find() will reuse an existing data node. This will typically
/// return a DataNode object and a DataNode.Link object. Often Find() is a static
/// method. Other times you create factory objects which will have a Find() method.
/// 2) You can call DataNode.Link.Release() when you are done with the DataNode. This
/// will stop the DataNode from making any callbacks to you. This will also decrement
/// a reference counter. That will tell the DataNode to release its resources after
/// the reference count goes to 0. Note that DataNode objects are shared, but you
/// create a new Link object each time you call Find().
/// 3) You can ask the DataNode for its data. That's different for different DataNode
/// classes. For the most part you can ask for this at any time. There might be
/// restrictions, like only call this in a certain thread. And most DataNode classes
/// have a way to say "invalid", "nothing yet, try again", or something like that.
/// Often you want to call this in the following callback, but that's not required.
/// 4) The DataNode can give you a callback when its data changes. This is a standard
/// Action callback, and is the same for all DataNode classes. If the caller needs
/// more information, create a delegate.
///
/// Sharing data nodes is essential. We make it very cheap and easy to Find() the data
/// you need. If you need data, you shouldn't try to think about what other parts of your
/// program already have that data. Instead you should just ask for it from the source.
/// We can only do that because sharing DataNodes is so cheap. Think about all the extra
/// objects that would exist if every request created a new DataNode. Remember that
/// DataNodes are usually built on top of other data nodes. So when you ask for the price
/// if MSFT you might be indirectly creating a lot of objects.
///
/// This is based heavily on
/// cpp_alert_server/source/generate_alerts/misc_framework/DataNodes.C. That's how
/// we manage data in the program that generates alerts. This idea has been evolving
/// for a long time, and started with GUI programs. Here are a few important differences
/// between this version and the C++ version:
/// 1) In C++ we had to create our own variant type. We needed that for the key in
/// the data structure that holds all known DataNode objects. In C# we're going
/// to use an object for the key. C# gives us more. Notice the StaticList class
/// which is helpful but not required for creating keys.
/// 2) The C++ version almost exclusively worked in one thread. There were special
/// routines to help move data into that thread. We are following the normal C#
/// conventions and allowing more access from more threads. In particular, the old
/// GUI versions would typically create DataNode objects from the GUI thread, while
/// most of the work was done in a seperate DataNode thread. We assume that this
/// will look similar to the older GUI versions. You need to access the same objects
/// from the GUI thread and a data thread. We use locks to protect the shared data,
/// like DataNode._all. Individual DataNode classes can add additonal restrictions
/// on their users.
/// 3) The old GUI versions had special support for sending data to the GUI. Among other
/// things, this was a place where we could slow down the events. If MSFT has 5
/// trades in a row, most DataNode objects will see all 5 events. But the code that
/// moves events to the GUI thread might only send one update. The result will be
/// the same as if the user looked away for just a second: you might not see all
/// of the updates, but the current state is correct. I'm planning to add something
/// similar here. We definately need some support routines to make it easier to
/// cross that barier. I'm not sure if the performance issues are as important as
/// they once were, but I'm still planning to add some of that.
/// 4) The C++ code had a DataNodeManager class. That mostly managed the thread. We
/// don't have anything quite like that because in this version other parts of the
/// program manage the threads. Some items have moved to static items in the
/// DataNode class, like DataNode._all. StockDataManager, a typical source for our
/// data, handles a lot of the threading stuff that DataNodeManager handled in C++.
/// 5) The C++ code had a special listener class for the callbacks. That's a place
/// where C# makes things much easier than C++. We just use Action for callbacks.
/// 6) The C++ code, and other legacy systems, all had the idea of broadcast messages.
/// A lot of that was just getting a message into a thread. As I've said, this C#
/// implementation doesn't typically manage threads. The other issue in C++ and
/// Delphi was finding the target of a message. For example, we'd create a new
/// DataNode in the GUI thread. The constructor would mark all data as invalid.
/// Then the constructor would send itself a message, telling itself to wake up in
/// a different thread and do the rest of it's initialization. In C# that's trivial
/// with a delegate. The delegate will automatically capture a pointer to the
/// DataNode. If we tried to use a pointer in Delphi or C++ there was danger that
/// the item would be deleted before the message arrived, and when we tried to
/// delever the message we'd be accessing an invalid pointer. Again, C# gives us
/// autoamtic garbage collection, so that problem goes away.
///
public class DataNode
{
// Notes on locks:
// 1) It is legal to lock _listeners while already holding a lock on _all.
// 2) Except for rule #1, you cannot request a lock if you are holding a different lock.
///
/// A description of all data nodes.
/// This is only for debugging. And this only makes sense when there are only a
/// small number of these.
///
///
/// Something userfriendly. Probably includes \r\n between items.
/// Details are likely to change from one version to the next.
///
public static string DebugDumpAll()
{
StringBuilder sb = new StringBuilder();
lock (_all)
{
sb.Append(_all.Count).Append(" DataNode objects:");
foreach (var kvp in _all)
sb.Append("\r\n").Append(kvp.Key);
}
return sb.ToString();
}
private static readonly Dictionary _all = new Dictionary();
public interface Link
{
void Release();
}
private class LinkImpl : Link
{
private readonly DataNode _dataNode;
private readonly Action _listener;
void Link.Release()
{
_dataNode.RemoveLink(this);
}
public LinkImpl(DataNode dataNode, Action listener)
{
_dataNode = dataNode;
_listener = listener;
}
public void NotifyListener()
{
if (null != _listener)
try
{
_listener();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
}
public bool HasListener { get { return null != _listener; } }
}
public interface IReplaceWith
{
///
/// This is aimed at Factory objects. Some objects are incomplete but read only.
/// ReplaceWith will not modify the original object, but it will return a new
/// object. The new object is just like the original, except that the replacements
/// have been made.
///
/// Note that we aren't looking for strings. We are looking for PlaceHolder objects.
/// Each placehold has a name, which is a string.
///
///
///
object ReplaceWith(Dictionary replacements);
///
/// True if we can't find any PlaceHolder objects.
///
/// If you try to Find() a factory and it has placeholders, that will throw an
/// exception. This lets us check first so we can present the user with a reasonable
/// error message.
///
/// Note, Find() could still fail with an exception. You should always prepair for
/// that. This function only helps in some cases.
///
/// Remember that we're letting the user take more control of the process of building
/// these objects. Normally this would be an assertion failed. But now we need to
/// present nice error messages and make sure the rest of the program doesn't stop.
///
///
bool ReplacementsComplete();
}
[XmlSerializerInit("*")]
public class PlaceHolder : IReplaceWith, XmlSerializer.Self
{
public override int GetHashCode()
{
return Key.GetHashCode();
}
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
return true;
PlaceHolder otherAsPlaceHolder = obj as PlaceHolder;
if (null == otherAsPlaceHolder)
return false;
return Key == otherAsPlaceHolder.Key;
}
public readonly string Key;
public PlaceHolder(string key)
{
Key = key;
}
object IReplaceWith.ReplaceWith(Dictionary replacements)
{
object replacement;
if (replacements.TryGetValue(Key, out replacement))
return replacement;
else
return this;
}
bool IReplaceWith.ReplacementsComplete()
{
return false;
}
public override string ToString()
{
StringBuilder result = new StringBuilder();
result.Append("PlaceHolder(").Append(Key).Append(')');
return result.ToString();
}
private const string PLACE_HOLDER = "PLACE_HOLDER";
public void Encode(XmlElement body)
{
body.SetProperty(XmlSerializer.TYPE, PLACE_HOLDER);
body.SetProperty(XmlSerializer.VALUE, Key);
}
private static void XmlSerializerInit()
{
XmlSerializer.RegisterDecoder(PLACE_HOLDER, Decode);
}
private static object Decode(XmlElement top)
{
String value = top.Property(XmlSerializer.VALUE, null);
if (null == value)
throw new ArgumentException("Unable to find VALUE for PlaceHolder.");
return new PlaceHolder(value);
}
public static readonly PlaceHolder SYMBOL = new PlaceHolder("Symbol");
}
///
/// This is like a normal PlaceHolder, but the type of the object is DataNode.Factory.
/// Some functions always want to take a Factory as their input. If you try to replace
/// this place holder with something other than a Factory, it will throw an exception.
///
public class FactoryPlaceHolder : Factory, XmlSerializer.Self
{
public readonly string Key;
public object MetaData
{
get
{ // There's no great answer here. This is used by GuiDataNode.GetFacory() to
// make some optimizations. GetFactory() wants the know the exact type of
// object this factory will turn into, and it wants to know now. But this
// is a placeholder and we can't know until later. This seems like a SAFE
// answer. Worst case we'll have an unnecessary GuiDataNode, but the result
// will be correct.
return typeof(FactoryPlaceHolder);
}
}
public FactoryPlaceHolder(string key)
{
Key = key;
}
object IReplaceWith.ReplaceWith(Dictionary replacements)
{
object replacement;
if (replacements.TryGetValue(Key, out replacement))
return (DataNode.Factory)replacement;
else
return this;
}
bool IReplaceWith.ReplacementsComplete()
{
return false;
}
public override string ToString()
{
StringBuilder result = new StringBuilder();
result.Append("PlaceHolder(").Append(Key).Append(')');
return result.ToString();
}
public void Encode(XmlElement body)
{
body.SetProperty(XmlSerializer.TYPE, "FACTORY_PLACE_HOLDER");
body.SetProperty(XmlSerializer.VALUE, Key);
}
public Link Find(out DataNode dataNode, Action callback = null)
{
throw new NotImplementedException();
}
}
///
/// Try to apply IReplaceWith.ReplaceWith().
///
///
/// Start from this object.
/// Presumably the result will be this object or something similar to it.
///
/// A list of replacements to make. This will be pass on to IReplaceWith.ReplaceWith()
///
/// Sometimes we know there's nothing for us to do. In that case we quickly return the original target, unmodified,
/// and we DON'T TOUCH the possiblyChanged argument. Other times it looks like there's work so we call
/// IReplaceWith.ReplaceWith() and we set possiblyChanged to true. You should initialize possiblyChanged to false before
/// calling this. Note: After calling ReplaceWithHelper() several times in a row possiblyChanged will be true if at least
/// one of the calls set it to true, which is what you want.
///
/// If you want to know for certain if something changed, try Equals(). We skip that step because Equals() can be
/// expensive.
///
///
/// Possibly a new object that looks just like the original, except for the requested replacements.
/// If no replacements apply, this might return the exact same object, or it might return a new object
/// that Equals() to original.
///
private static object ReplaceWithHelper(object target,
Dictionary replacements, ref bool possiblyChanged)
{
IReplaceWith t = target as IReplaceWith;
if (null == t)
return target;
else
{
possiblyChanged = true;
return t.ReplaceWith(replacements);
}
}
///
/// This is basically a delegate, with a couple of nice properties.
/// 1) Most of the data has been seperated from the function call. This allows us
/// to inspect and modify the Factory object.
/// 2) Delegates have terrible hash codes. This forces the creator to give another
/// object represent the delegate. We use that second object to help us build a
/// better hash code.
///
public interface Factory : IReplaceWith
{
///
/// Find an instance of the DataNode described by this factory. This might create
/// a new object, or it might reuse an existing object. This might throw an exception,
/// in particular if you haven't called ReplaceWith() on all of the placeholders.
///
/// Use this to access the data you requested.
///
/// This will be called each time the data changes. null means don't do anything
/// when the data changes. We have some optimizations for the null case.
///
/// Use this link to release the DataNode when you are done with it.
Link Find(out DataNode dataNode, Action callback = null);
///
/// This can return anything, including null.
///
/// In general this has no specific meaning. However, certain types of DataNode
/// classes give it meaning.
///
/// Mostly we use this to determine the type of a Factory. It is inconveneint to
/// create a new class for each type of Factory. (I've tried that!!) Instead we
/// add some type of tag, like a class or a unique object, and we store that tag
/// in the Factory. This could have additional information, but it almost
/// certainly needs to start by identifying the type of the Factory. What if you
/// read a value of 5? What does that mean? This is a delayed DataNode that
/// waits 5 seconds before passing on any notifications? This is a filter data
/// node that will tell you if the price is above a certain value, in this case
/// $5?
///
/// See the GuiDataNode class for an actual example where this is used.
///
object MetaData { get; }
}
///
/// 1) Most of the data has been seperated from the function call. This allows us
/// to inspect and modify the Factory object.
/// 2) Delegates have terrible hash codes. This forces the creator to give another
/// object represent the delegate. We use that second object to help us build a
/// better hash code.
///
protected internal class FactoryWithHash : Factory, XmlSerializer.Self
{
Link Factory.Find(out DataNode dataNode, Action callback)
{
lock (_all)
{
if (!_all.TryGetValue(this, out dataNode))
{
dataNode = _worker(_arguments);
dataNode._key = this;
_all[this] = dataNode;
}
// Don't release _all until we do this. We haven't updated the reference
// count yet. If we released _all it's possible that someone could drop
// this item's reference count
return dataNode.CreateLink(callback);
}
}
public delegate DataNode Worker(object arguments);
private readonly Worker _worker;
private readonly int _hashCode;
private readonly int _hashContribution;
private readonly object _arguments;
private readonly string _name;
public object Arguments { get { return _arguments; } }
///
/// This is required by the Factory interface.
///
public object MetaData { get; private set; }
///
/// Constructor.
///
/// The delegate which will actually create the new object.
///
/// This input will contribute to the hash code of the resulting object. GetHashCode()
/// for delegates is known to suck. Try to pick something that is unique to this worker.
///
/// For example, we expect that most DataNode classes will create exactly one type of
/// factory. The arguents might vary, but the DataNode will probably have exactly one
/// unique Worker. So use that class to get the hashContribution.
///
/// Note that we use worker to compute FactoryWithHash.Equals() but we use
/// hashContribution to compute FactoryWithHash.GetHashCode(). It is up to the caller
/// to make sure these two inputs are consistent.
///
///
/// This will be passed into the worker as its input. This can be anything, including
/// null. If you call ReplaceWith on this object, it will try to call ReplaceWith on
/// its arguments.
///
///
/// This is only used for debugging. If name is null, we'll try to get a
/// name from worker, but the result is surprisingly hard to read.
///
///
/// This is used to populate the MetaData field. This can be null.
///
private FactoryWithHash(Worker worker, int hashContribution, object arguments,
string name, object metaData)
{
_worker = worker;
_hashContribution = hashContribution;
_arguments = arguments;
if (null == arguments)
_hashCode = hashContribution ^ typeof(FactoryWithHash).GetHashCode();
else
_hashCode = hashContribution ^ typeof(FactoryWithHash).GetHashCode() ^ arguments.GetHashCode();
_name = name ?? worker.ToString();
MetaData = metaData;
}
private static string GetXmlName(Type type)
{
return type.Name;
}
///
/// Same as above. The only difference is that we let you supply a type for the hash
/// contribution. (Presumably this will be the class of the method that's calling
/// this constructor.) We get the hash code of that type. Also, we get the name from
/// that type. And the MetaData is that type.
///
///
///
///
public FactoryWithHash(Worker worker, Type hashContribution, object arguments) :
this(worker, hashContribution.GetHashCode(), arguments,
GetXmlName(hashContribution), hashContribution) { }
public override bool Equals(object obj)
{
if (object.ReferenceEquals(this, obj))
// Same instance.
return true;
FactoryWithHash other = obj as FactoryWithHash;
if (null == other)
// obj was null or obj was the wrong type.
return false;
return _worker.Equals(other._worker) && _arguments.Equals(other._arguments);
}
public override int GetHashCode()
{
return _hashCode;
}
public override string ToString()
{
StringBuilder result = new StringBuilder();
result.Append("Factory(").Append(_name).Append(", ").Append(_arguments).Append(')');
return result.ToString();
}
object IReplaceWith.ReplaceWith(Dictionary replacements)
{
bool possiblyChanged = false;
object replacementArgs = ReplaceWithHelper(_arguments, replacements, ref possiblyChanged);
if (possiblyChanged)
return new FactoryWithHash(_worker, _hashContribution, replacementArgs, _name, MetaData);
else
return this;
}
bool IReplaceWith.ReplacementsComplete()
{
IReplaceWith r = _arguments as IReplaceWith;
if (r == null)
return true;
else
return r.ReplacementsComplete();
}
private const string ARGS = "ARGS";
public static XmlElement GetArgs(XmlElement factory)
{
XmlElement result = factory.Node(ARGS);
if (null == result)
throw new ArgumentException("Could not find ARGS in DataNode.Factory.");
return result;
}
///
/// Use this if you want to use a custom function to translate this type from XML back
/// to a FactoryWithHash object.
///
/// Consider using RegisterStandardDecoder() or [XmlSerializerInit]. Those will
/// automatically do a lot of the work for you, and in most cases that's all you need.
///
/// Note that FactoryWithHash has it's own encoder which is always used. Your
/// decoder should be compatible with that.
///
///
///
public static void RegisterDecoder(Type factoryType, Func decoder)
{
XmlSerializer.RegisterDecoder(GetXmlName(factoryType), decoder);
}
///
/// This will create and register a decoder for a certain type of object.
///
/// The assumption is that the factory contains a StaticList of ojects. These will
/// be fed back into a static public method called GetFactory(). If the class has more
/// than one function with that name, we should be able to figure out which one to call
/// based on the type and number of objects in the StaticList.
///
/// Normally you'd tag the class with [XmlSerializerInit] rather than calling this
/// function directly. However, you might want to do additional work in the the
/// initialization phase. Then you can say [XmlSerializerInit(MyInitFunction)].
/// MyInitFunction can handle the other work, then call RegisterStandardDecoder().
///
///
/// This is typically the type of the DataNode. This should be the same type as used
/// in the hashContribution argument to the FactoryWithHash constructor.
///
public static void RegisterStandardDecoder(Type factoryType)
{
RegisterDecoder(factoryType, xml => StandardDecoder(factoryType, xml));
}
private static DataNode.Factory StandardDecoder(Type type, XmlElement source)
{
object args = XmlSerializer.Decode(GetArgs(source));
try
{ // Look for a GetFactory() method with one argument matching the argument
// we just read. Notice the order in which we check. It is possible that
// someone created a Factory for a ConstantDataNode and the argument was
// a StaticList. In that case we want to call GetFactory() directly on
// that list, not on the members of that list.
object[] argsAsArray = new object[] { args };
object result = type.InvokeMember("GetFactory",
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public | BindingFlags.OptionalParamBinding,
null, null, argsAsArray);
return (DataNode.Factory)result;
} catch { }
{
StaticList argsAsList = args as StaticList;
if (null == argsAsList)
throw new ArgumentException("Unable to reconstruct Factory for type " + type + " from object of type "
+ ((null == args) ? "null" : args.GetType().ToString()));
// ARGS is of type StaticList, so look for a GetFactory() method whose signature
// matches the elements of that list.
object[] argsAsArray = argsAsList.GetItems();
object result = type.InvokeMember("GetFactory",
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public | BindingFlags.OptionalParamBinding,
null, null, argsAsArray);
return (DataNode.Factory)result;
}
}
public void Encode(XmlElement body)
{
body.SetProperty(XmlSerializer.TYPE, _name);
XmlElement args = body.NewNode(ARGS);
XmlSerializer.Encode(_arguments, args);
}
}
///
/// A list that's appropriate for a key in a hash table. The list can never change. The
/// hash code is computed once when the list is created and then cached. The list
/// recursively inspects each of its items to compute the hash code and to implement
/// Equals(). We can't enforce it, but this only works if the individual items never
/// change.
///
[XmlSerializerInit("*")]
public class StaticList : IReplaceWith, IEnumerable, XmlSerializer.Self
{
private readonly int _hashCode;
private readonly object[] _items;
///
/// Don't modify this array!
///
///
public object[] GetItems()
{
return _items;
}
///
/// Mostly aimed at debugging.
///
/// The contents of the list in a human readable form.
public override string ToString()
{
StringBuilder result = new StringBuilder("StaticList[");
bool first = true;
foreach(object item in _items)
{
if (first)
first = false;
else
result.Append(", ");
if (null == item)
result.Append("null");
else if (item is TransformationDataNode.Transformation)
// Perhaps this should apply to any Delegate type?
result.Append(((TransformationDataNode.Transformation)item).Method);
else
result.Append(item);
}
result.Append(']');
return result.ToString();
}
public StaticList(System.Collections.IList source)
{
_items = new object[source.Count];
source.CopyTo(_items, 0);
// If you need to create a hash code based on a list of items, some of which might be
// null, consider using the Hasher class instead of copying this code. It does pretty
// much the same thing as this, but it's easier to reuse.
uint hashCode = (uint)typeof(StaticList).GetHashCode();
foreach (object item in _items)
{
hashCode = (hashCode << 3) | (hashCode >> 29);
if (null != item)
hashCode |= (uint)item.GetHashCode();
}
_hashCode = (int)hashCode;
}
public static StaticList Create(params object[] items)
{ // This is a little silly. We want an array of objects, and we're starting
// with an array of objects. But we make a copy anyway. The problem is that
// we want a new array that no one else can touch.
return new StaticList(items);
}
public override int GetHashCode()
{
return _hashCode;
}
///
/// A StaticList can only be equal to another StaticList.
/// Recursively compare the items in this list to the other list.
/// Uses a few optimizations.
///
/// May be null
///
public override bool Equals(object obj)
{
if (object.ReferenceEquals(this, obj))
// Same instance.
return true;
StaticList other = obj as StaticList;
if (null == other)
// Different type
return false;
return Equals(other);
}
///
/// Recursively compare the items in this list to the other list.
///
/// May not be null.
///
private bool Equals(StaticList other)
{
if (_hashCode != other._hashCode)
return false;
if (_items.Length != other._items.Length)
return false;
for (int i = 0; i < _items.Length; i++)
if (!object.Equals(_items[i], other._items[i]))
return false;
return true;
}
object IReplaceWith.ReplaceWith(Dictionary replacements)
{
bool possiblyChanged = false;
List newItems = new List(_items.Length);
foreach (object item in _items)
newItems.Add(ReplaceWithHelper(item, replacements, ref possiblyChanged));
if (possiblyChanged)
return new StaticList(newItems);
else
return this;
}
// for IReplaceWith
public bool ReplacementsComplete()
{
foreach (object item in _items)
if (item is IReplaceWith)
if (!((IReplaceWith)item).ReplacementsComplete())
return false;
return true;
}
public StaticList ReplaceWith(Dictionary replacements)
{
return (StaticList)((IReplaceWith)this).ReplaceWith(replacements);
}
public object this[int i] { get { return _items[i]; } }
public int Length { get { return _items.Length; } }
public static void Extract(object source, out T0 v0, out T1 v1, out T2 v2, out T3 v3, out T4 v4)
{
StaticList list = (StaticList)source;
System.Diagnostics.Debug.Assert(list.Length == 5);
v0 = (T0)list[0];
v1 = (T1)list[1];
v2 = (T2)list[2];
v3 = (T3)list[3];
v4 = (T4)list[4];
}
public IEnumerator GetEnumerator()
{
return ((IEnumerable)_items).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_items).GetEnumerator();
}
private const string STATIC_LIST = "static_list";
public void Encode(XmlElement body)
{
body.SetProperty(XmlSerializer.TYPE, STATIC_LIST);
int i = 0;
foreach (object item in _items)
{
XmlElement child = body.NewNode("ITEM_" + i);
i++;
XmlSerializer.Encode(item, child);
}
}
private static IList DecodeChildren(XmlElement body)
{
List children = new List();
foreach (XmlElement child in body.Enum())
children.Add(XmlSerializer.Decode(child));
return children;
}
public StaticList(XmlElement body) : this(DecodeChildren(body))
{
}
private static void XmlSerializerInit()
{
XmlSerializer.RegisterDecoder(STATIC_LIST, args => new StaticList(args));
}
}
///
/// The request used to Find() this DataNode. Use this copy when we clean up. Before
/// this object destroyes itsef it needs to remove itself from _all using this key.
///
private Factory _key;
// The people we may have to notify.
private readonly HashSet _listeners = new HashSet();
// Links to us which have no callbacks. These were seperated from _listeners
// strictly as an optimization.
//
// For simplicity let's say you have to lock _listeners any time you want to
// access _listeners or _otherOwners. I'm thinking about a case when someone
// is trying to add one time of link and someone in a different thread is
// trying to remove the other type of link. If someone asked "are we done
// with this object?" how could we know for sure.
private int _otherOwners;
// One more listener.
private void AddLink(LinkImpl link)
{
//System.Diagnostics.Debug.WriteLine("AddLink(" + _key + ")");
lock (_listeners)
{
if (link.HasListener)
_listeners.Add(link);
else
_otherOwners++;
}
}
// This severs the connection to the given link. It should only be called
// by a Link. If this is the last link, it will cause the DataNode to
// release its resources.
private void RemoveLink(LinkImpl link)
{
//System.Diagnostics.Debug.WriteLine("RemoveLink(" + _key + ")");
bool needToRelease = false;
lock (_all)
{
lock (_listeners)
{
bool okay;
if (link.HasListener)
okay = _listeners.Remove(link);
else
{
_otherOwners--;
okay = _otherOwners >= 0;
}
System.Diagnostics.Debug.Assert(okay);
needToRelease = ReadyForRelease();
}
if (needToRelease)
_all.Remove(_key);
}
// Outside of the lock.
if (needToRelease)
Release();
}
///
/// This gets called after the last listener disconnects. This will do
/// various cleanup including calling CleanUp(). Originally CleanUp() and
/// Release() were the same call. But I was worried that people would
/// forget to call base.Release();
///
private void Release()
{
//System.Diagnostics.Debug.WriteLine("Release(" + _key + ")");
CleanUp();
lock (_automaticLinks)
{
foreach (Link link in _automaticLinks)
link.Release();
_automaticLinks.Clear();
}
}
///
/// This gets called after the last listener disconnects. This means
/// to release any resources we've been using. Override this if your
/// class needs to release any special resources, but be sure to call
/// this original function.
///
protected virtual void CleanUp()
{
}
// This is true if there are no listeners or other reasons to live.
private bool ReadyForRelease()
{
lock (_listeners)
{
return (_otherOwners <= 0) && (_listeners.Count == 0);
}
}
protected void NotifyListeners()
{
List listeners;
lock (_listeners)
{
listeners = new List(_listeners);
}
foreach (LinkImpl link in listeners)
// NotifyListener() is responsible for catching exceptions.
link.NotifyListener();
}
///
/// Be careful with your constructors. You are almost certainly holding a
/// lock on _all while your constructor is running.
///
/// Never construct a DataNode object directly. You want to do it as part
/// of the Find() / Link mechanism.
///
protected DataNode()
{
}
///
/// This creates a new listener. It returns a DataNode.Link as the recipt.
/// The recipt can be used to terminate the connection to this data node. The
/// listener is called whenever the data in this data node changes, except
/// when the link is terminated.
///
/// Can be null.
/// A new Link object.
private Link CreateLink(Action listener)
{
LinkImpl result = new LinkImpl(this, listener);
AddLink(result);
return result;
}
// Automatic links create simipler code. They are not strictly required, but
// they help. All items in this list will be released as soon as the last
// link to this data node is released
private readonly List _automaticLinks = new List ();
protected void AddAutoLink(Link link)
{
lock (_automaticLinks)
{
_automaticLinks.Add(link);
}
}
private object _value;
///
/// This is a convenient way to share data with the caller. A class can add
/// other ways. Having a standard interface helps code reuse. For example a
/// cache DataNode could be a wrapper around any other DataNode that uses
/// this function.
///
/// If you used a GenericDataNode in a previous version of the software,
/// just use an ordinary DataNode and GetValue() in this version.
///
/// Value can be null. Assume that's common. Most DataNode objects get
/// their data from a server. Before the server gives us a result, the Value
/// will be null. null can mean other things, too, so you should always be
/// ready for that.
///
/// The default implementation stores the value in a variable and notifies
/// listeners any time that variable changes. However, a DataNode object
/// could override this and keep the interface. Perhaps you want to defer
/// some of the computation until the first time someone asks for the value.
///
/// It seems like get has to be thread safe. That's unfortunate because it
/// makes delayed computation hard or impossible. But how else could a
/// GuiDataNode initialize itself?
///
public object Value
{
get { return _value; }
protected set
{
//if ((null == value) || (value.ToString() == ""))
// System.Diagnostics.Debug.WriteLine("Here");
// Same as (!_value.Equals(value)), but the following works with
// nulls, too.
if (!object.Equals(_value, value))
{
//System.Diagnostics.Debug.WriteLine(DateTime.Now + ": Value " + _value + " => " + value + " for " + _key);
_value = value;
NotifyListeners();
}
//else
// System.Diagnostics.Debug.WriteLine(DateTime.Now + ": Value " + _value + " unchanged for " + _key);
}
}
///
/// Calling List.Equals() will only tell you if the two lists are the same object.
/// It does not look at the contents.
///
/// This function walks through the lists and returns true if the lists are the same
/// size and each item in the first list is the same as the corresponding item in the
/// second list.
///
/// We use object.Equal(object, object) to compare the items in the list. So this is
/// not a deep compare. In particular if an item in a list is another list, we don't
/// call this function recursively.
///
/// If both of the inputs are null, this returns true. If exactly one of the inputs
/// is null, this returns false.
///
///
/// The type of the items in the first list. We always use it as an object, so the
/// type doesn't matter. I would have used a ? for the item type if I was in Java.
///
///
/// The type of the items in the second list. We always use it as an object, so the
/// type doesn't matter. I would have used a ? for the item type if I was in Java.
///
/// The first list. May be null.
/// The second list. May be null.
///
/// True if the items in the first list are Equal() to the items in the second list
/// or if both lists are null.
///
public static bool ListContentsEqual(IList a, IList b)
{
if (object.ReferenceEquals(a, b))
return true;
if ((null == a) || (null == b))
return false;
if (a.Count != b.Count)
return false;
for (int i = a.Count - 1; i >= 0; i--)
if (!object.Equals(a[i], b[i]))
return false;
return true;
}
///
/// This is like saying "Value = newList". The difference is that we use
/// ListContentsEqual() to make sure the lists are really different before sending
/// out a notification. This is just an optimization.
///
///
/// The type of the items in the list. Note that both the current value and the
/// new value both must be of type IList(T).
///
/// The new value for this DataNode. May be null.
protected void SetListValue(IList newList)
{
if (!ListContentsEqual((IList)Value, newList))
Value = newList;
}
}
///
/// If you wish to register your class with the XmlSerializer, you need to do that
/// in a special way. Add [XmlSerializerInit] above your class definition. Then
/// create a method starting "private static void XmlSerializerInit()". In that
/// method make your calls to XmlSerializer.RegisterEncoder(),
/// XmlSerializer.RegisterDecoder(), and XmlSerializer.AddKnownObject(). That
/// will ensure that there are no threading issues and that everything gets registered
/// before someone needs it.
///
[AttributeUsage(AttributeTargets.Class)]
public class XmlSerializerInitAttribute : System.Attribute
{
///
/// The work to do when the XmlSerializer is initializing.
/// The default is null. This means to call
/// FactoryWithHash.RegisterStandardDecoder() on the class.
/// Alternatively you can give the name of a static private method in the class.
/// In that case we will call that method on this class. That method should call
/// RegisterStandardDecoder() if this is a DataNode.Factory, but can also do
/// additional work.
/// If this is set to "*" that's the same as setting it to "XmlSerializerInit".
/// That's just a convenience to save some typing and to suggest a standard name
/// that's easy for the programmer to recognize.
///
public readonly string MethodName;
///
///
///
///
/// The method in this class to call when the XmlSerializer is initializing.
/// TODO add more
///
public XmlSerializerInitAttribute(string methodName = null)
{
if ("*" == methodName)
// A convenience. You could have written out the entire string.
methodName = "XmlSerializerInit";
MethodName = methodName;
}
}
///
/// Translate between objects and XML.
///
/// Not all object types are supported. This is primarily aimed at DataNodeFactory objects.
///
/// This must be cross platform. We might write from GWT and read in C#, or vice versa.
///
public static class XmlSerializer
{
private static bool _frozen;
///
/// Some objects know how to encode themselves.
///
public interface Self
{
///
/// Convert yourself to XML.
///
///
/// Typically the parent creates and names the XML element while the object being encoded
/// fills in the details.
///
void Encode(XmlElement body);
}
static public void Encode(object toEncode, XmlElement body)
{
System.Diagnostics.Debug.Assert(_frozen);
if (null == toEncode)
{
body.SetProperty(TYPE, NULL_TYPE);
return;
}
string name;
if (_knownObjectsByValue.TryGetValue(toEncode, out name))
{
body.SetProperty(TYPE, OBJECT_TYPE);
body.SetProperty(VALUE, name);
return;
}
if (toEncode is Self)
{
(toEncode as Self).Encode(body);
return;
}
Action encoder;
if (_allEncoders.TryGetValue(toEncode.GetType(), out encoder))
{
encoder(toEncode, body);
return;
}
throw new ArgumentException("Can't encode items of type " + toEncode.GetType() + ", object=" + toEncode);
}
private static void EncodeInt(object toEncode, XmlElement body)
{ // Note: Casting from an object to an Int64 is different from casting from a number
// to an Int64.
// int i = 5;
// object o = i;
// int64 thisWorks = (Int64)i;
// int64 thisThrowsAnException = (Int64)o;
body.SetProperty(VALUE, (Int64)toEncode);
body.SetProperty(TYPE, INT_TYPE);
}
private static void EncodeInt32(object toEncode, XmlElement body)
{ // Int64 is our preferred type for data. But Int32 is used for some configuration settings,
// like the number of digits to show after the decimal point.
body.SetProperty(VALUE, (Int32)toEncode);
body.SetProperty(TYPE, INT32_TYPE);
}
private static void EncodeDouble(object toEncode, XmlElement body)
{
body.SetProperty(VALUE, (double)toEncode);
body.SetProperty(TYPE, DOUBLE_TYPE);
}
private static void EncodeColor(object toEncode, XmlElement body)
{
Color asColor = (Color)toEncode;
body.SetProperty(VALUE, asColor);
body.SetProperty(TYPE, COLOR_TYPE);
body.SetProperty("debug", asColor.Name);
}
private static void EncodeString(object toEncode, XmlElement body)
{
String asString = (String)toEncode;
body.SetProperty(VALUE, asString);
body.SetProperty(TYPE, STRING_TYPE);
}
private static readonly Dictionary _knownObjectsByValue = new Dictionary();
private static readonly Dictionary _knownObjectsByName = new Dictionary();
///
/// This will iterate over each member of the enumeration, and call AddKnownObject() on
/// each one.
///
/// The enumeration to examine.
///
/// This is added to the front of each item to get the item's XML name.
/// The default is to use the type. The default will look like valid,
/// fully qualified, C# identifiers, e.g.
/// TradeIdeas.TIProGUI.DataNodeRectangleViewer.SecondValueMeaning.Width
///
public static void AddEnum(Type type, string prefix=null)
{
System.Diagnostics.Debug.Assert(type.IsEnum);
if (null == prefix)
// This is consistant with C# source code. A completely namespace, list of nested
// classes, the name of the enum, and the name of the item, all seperated by periods.
prefix = type.FullName.Replace('+', '.') + '.';
foreach (Object value in type.GetEnumValues())
AddKnownObject(value, prefix + value.ToString());
}
///
/// This is an object that we know how to serialize.
/// This is something that has to be consistent between different versions of the program.
/// Any time have to serialize the given object, we use the given name in XML.
///
/// The object that we want to describe.
///
/// A description of the object.
/// This exists in its own namespace; it only has to be unique with respect to other
/// calls to AddKnownObject().
///
public static void AddKnownObject(object o, string name)
{
System.Diagnostics.Debug.Assert(!_frozen);
System.Diagnostics.Debug.Assert(!(_knownObjectsByName.ContainsKey(name) || _knownObjectsByValue.ContainsKey(o)));
_knownObjectsByValue[o] = name;
_knownObjectsByName[name] = o;
}
///
/// This has the same result calling the other version of AddKnownObject.
/// C# will not automatically convert a method name to an object in a function call.
/// Before we added this, each caller would manually cast the method name to
/// TransformationDataNode.Transformation, then C# would automatically convert
/// that to Object.
///
///
///
public static void AddKnownObject(TransformationDataNode.Transformation t, string name)
{
AddKnownObject((object)t, name);
}
static private readonly Dictionary> _allEncoders =
new Dictionary>
{
{ typeof(Int64), EncodeInt },
{ typeof(Int32), EncodeInt32 },
{ typeof(double), EncodeDouble },
{ typeof(Color), EncodeColor },
{ typeof(string), EncodeString }
};
static public void RegisterEncoder(Type type, Action encoder)
{
System.Diagnostics.Debug.Assert(!_frozen);
if (typeof(Self).IsAssignableFrom(type))
// This would be ambiguous. If you have a rule for class X and class X implements
// self, which rule would you use? I can't think of any good reason for adding
// such a rule, so I just make it illegal.
throw new ArgumentException("Always uses the Self interface and ignores this.");
_allEncoders.Add(type, encoder);
}
///
/// We use this property to say what type of object we are encoding / decoding.
/// This property is required. If it is missing or invalid Decode() will throw an exception.
///
public const string TYPE = "TYPE";
///
/// This property is commonly used to store the data for a specific type. Each type
/// can make its own decision, but this is convenient for a a lot of types.
///
/// When this property is used convention dictates that it is required.
/// If it is missing or invalid Decode() should throw an exception.
///
public const string VALUE = "VALUE";
private const string INT_TYPE = "int";
private const string INT32_TYPE = "int32";
private const string DOUBLE_TYPE = "double";
private const string COLOR_TYPE = "color";
private const string STRING_TYPE = "string";
private const string NULL_TYPE = "null";
private const string OBJECT_TYPE = "object";
private static readonly Type[] EMPTY_LIST = new Type[0];
static XmlSerializer()
{
System.Diagnostics.Debug.WriteLine("Initializing XmlSerializer");
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
string definedIn = typeof(XmlSerializerInitAttribute).Assembly.GetName().Name;
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
// Note that we have to call GetName().Name. Just GetName() will not work. The following
// if statement never run when I tried to compare the results of GetName().
if ((!assembly.GlobalAssemblyCache) && ((assembly.GetName().Name == definedIn) || assembly.GetReferencedAssemblies().Any(a => a.Name == definedIn)))
foreach (Type type in assembly.GetTypes())
{
object[] attributes = type.GetCustomAttributes(typeof(XmlSerializerInitAttribute), false);
if (attributes.Length > 0)
{
System.Diagnostics.Debug.Assert(attributes.Length == 1);
XmlSerializerInitAttribute attribute = (XmlSerializerInitAttribute)attributes[0];
if (null == attribute.MethodName)
// The common case. Make this easy for the person writing the class.
DataNode.FactoryWithHash.RegisterStandardDecoder(type);
else
{
MethodInfo methodInfo = type.GetMethod(attribute.MethodName, BindingFlags.NonPublic | BindingFlags.Static, null, Type.EmptyTypes, null);
if ((null == methodInfo) || !methodInfo.IsStatic)
throw new Exception("Could not find method private static void " + type.FullName + "." + attribute.MethodName + "()");
methodInfo.Invoke(type, EMPTY_LIST);
}
}
}
stopwatch.Stop();
System.Diagnostics.Debug.WriteLine("XmlSerializer finished after "
+ stopwatch.Elapsed.TotalSeconds);
_frozen = true;
}
static public object Decode(XmlElement top)
{ // The name of the object is now owned by the parent. That makes it easy for the parent
// object to have multiple children, some of which are optional. You can look up children
// by name or index!
System.Diagnostics.Debug.Assert(_frozen);
string type = top.Property(TYPE, "not specified");
Func parser;
_allDecoders.TryGetValue(type, out parser);
if (null == parser)
throw new ArgumentException("Unknown object type: " + type);
return parser(top);
}
static private readonly Dictionary> _allDecoders =
new Dictionary>
{
{ INT_TYPE, DecodeInt },
{ INT32_TYPE, DecodeInt32 },
{ DOUBLE_TYPE, DecodeDouble },
{ NULL_TYPE, delegate { return null; } },
{ COLOR_TYPE, DecodeColor },
{ STRING_TYPE, DecodeString },
{ OBJECT_TYPE, DecodeObject }
};
static public void RegisterDecoder(string name, Func decoder)
{
System.Diagnostics.Debug.Assert(!_frozen);
_allDecoders.Add(name, decoder);
}
private static object DecodeInt(XmlElement top)
{
Int64? value = top.PropertyInt(VALUE);
if (!value.HasValue)
throw new ArgumentException("Unable to find VALUE for int.");
return value.Value;
}
private static object DecodeInt32(XmlElement top)
{
Int32? value = top.PropertyInt32(VALUE);
if (!value.HasValue)
throw new ArgumentException("Unable to find VALUE for int.");
return value.Value;
}
private static object DecodeDouble(XmlElement top)
{
double? value = top.PropertyDouble(VALUE);
if (!value.HasValue)
throw new ArgumentException("Unable to find VALUE for double.");
return value.Value;
}
private static object DecodeColor(XmlElement top)
{
Color? value = top.PropertyColor(VALUE);
if (!value.HasValue)
throw new ArgumentException("Unable to find VALUE for color.");
return value.Value;
}
private static object DecodeString(XmlElement top)
{
string value = top.Property(VALUE, null);
if (null == value)
// In a lot of places our XML reader tries to use a default.
// We explicitly are not allowing any defaults at this time.
// It doesn't seem to add much.
throw new ArgumentException("Unable to find VALUE for string");
return value;
}
private static object DecodeObject(XmlElement top)
{
string value = top.Property(VALUE, null);
if (null == value)
// The standard says to throw an exception if we can't find the VALUE.
throw new ArgumentException("Unable to find VALUE for object");
object result;
if (!_knownObjectsByName.TryGetValue(value, out result))
// The standard says to throw an exception if we can't parse the VALUE.
// We can have some optional fields, espeically if the format changes over
// time, but if there's an obvious problems we should NOT return a default.
throw new ArgumentException("Unable to find object of type " + VALUE);
else
return result;
}
public static string DebugGetKnownTypes()
{
StringBuilder result = new StringBuilder();
foreach (var kvp in _allEncoders)
{
result.Append(kvp.Key.Name);
result.Append("\r\n");
}
return result.ToString();
}
public static string DebugGetKnownTypeStrings()
{
StringBuilder result = new StringBuilder();
foreach (var kvp in _allDecoders)
{
result.Append(kvp.Key);
result.Append("\r\n");
}
return result.ToString();
}
public static string DebugGetKnownObjects()
{
StringBuilder result = new StringBuilder();
foreach (var kvp in _knownObjectsByName)
{
result.Append(kvp.Key);
result.Append(" <==> ");
result.Append(kvp.Value);
result.Append("\r\n");
}
return result.ToString();
}
}
[XmlSerializerInit("*")]
public class TransformationDataNode : DataNode
{
public static object StringToInt(object input)
{
string asString = input as String;
// Note: If the value is not a string, but we're expecting a string, ignore it.
// Treat it like a null value. That probably would mean a programming error.
if (null == asString)
return null;
Int64 asInt;
if (ServerFormats.TryParse(asString, out asInt))
return asInt;
else
// Not a valid integer.
return null;
}
public static object StringToDouble(object input)
{
string asString = input as String;
if (null == asString)
return null;
double asDouble;
if (ServerFormats.TryParse(asString, out asDouble))
return asDouble;
else
// Not a valid double.
return null;
}
public static object StringToTime(object input)
{
string asString = input as String;
if (null == asString)
return null;
else
return ServerFormats.DecodeServerTime(asString);
}
private static void XmlSerializerInit()
{
XmlSerializer.AddKnownObject(StringToInt, "StringToInt");
XmlSerializer.AddKnownObject(StringToDouble, "StringToDouble");
XmlSerializer.AddKnownObject(StringToTime, "StringToTime");
FactoryWithHash.RegisterStandardDecoder(typeof(TransformationDataNode));
}
public delegate object Transformation(object input);
private DataNode _child;
private readonly Transformation _transformation;
private TransformationDataNode(object args)
{
StaticList argList = (StaticList)args;
System.Diagnostics.Debug.Assert(argList.Length == 2);
_transformation = (Transformation)argList[0];
AddAutoLink(((Factory)argList[1]).Find(out _child, Update));
Update();
}
///
/// For simplicity I'm copying, processing, and caching the data as soon as I get it.
/// Sometimes it makes more sense to wait until someone asks for the data.
///
private void Update()
{
try
{
Value = _transformation(_child.Value);
}
catch
{
Value = null;
}
}
public static Factory GetFactory(Transformation transformation, Factory input)
{
return new FactoryWithHash(args => new TransformationDataNode(args),
typeof(TransformationDataNode),
StaticList.Create(transformation, input));
}
}
///
/// A very simple data node. You tell it a value at creation time, and that is always the
/// Value read from this data node. This is useful when an object expects data nodes as
/// inputs, but in this case we want to give a constant value to that object. This
/// DataNode never sends an update message because its value never changes.
///
[XmlSerializerInit]
public class ConstantDataNode : DataNode
{
private ConstantDataNode(object args)
{
Value = args;
}
///
///
///
/// The value for the DataNode to return. This can be a PlaceHolder. This can be null.
/// If the value is not null, we will call GetHashCode() and Equals() on it.
///
///
public static Factory GetFactory(object value)
{
return new FactoryWithHash(args => new ConstantDataNode(args),
typeof(ConstantDataNode), value);
}
///
/// This is the inverse of GetFactory(). If you created an object by calling
/// GetFactory(x), and you call TryDeconstruct() on the factory, you should get
/// x back as the result. If the input was any other object, this will fail.
///
/// The object we are trying to deconstruct.
///
/// The value returned by the DataNode. Which WAS the input to GetFactory().
/// null is returned on failure.
/// Note: null can also be returned on success, if the orginal input was null.
///
///
/// True on success, false on failure.
///
public static bool TryDeconstruct(object factory, out object constantValue)
{
FactoryWithHash f = factory as FactoryWithHash;
if (null == f)
{
constantValue = null;
return false;
}
if (!typeof(ConstantDataNode).Equals(f.MetaData))
{
constantValue = null;
return false;
}
constantValue = f.Arguments;
return true;
}
///
/// This is similar to the two parameter version of TryDeconstruct().
/// After finding the original value we try to convert that to the type T.
/// If we can't find the original value or the original value was not of
/// the right type, we return the default value for T.
///
/// If you are expecting a value type, you can specify that type directly,
/// or you can specifcy the nullable version of that type. Either will
/// work the same on success. But the nullable type will return null
/// on error.
///
/// The type we want the result to be.
/// The object to deconstruct.
///
/// The original input to GetFactory(), or default(T) on failure.
///
public static T TryDeconstruct (object factory)
{
object contents;
bool success = TryDeconstruct(factory, out contents);
if (!success)
return default(T);
if (!(contents is T))
return default(T);
return (T)contents;
}
}
///
/// This takes a single streaming input and returns one of four constants.
///
/// This was aimed at picking a color. Often you want to display something in green if it's
/// positive, red if it's negative, black or white if it's 0.
///
[XmlSerializerInit]
public class CompareToZeroDataNode : DataNode, IFormatProvider
{
private readonly object _greater;
private readonly object _equal;
private readonly object _less;
private readonly object _ifNull;
private DataNode _value;
private CompareToZeroDataNode(object args)
{
StaticList list = (StaticList)args;
Factory factory = (Factory)list[0];
_greater = list[1];
_equal = list[2];
_less = list[3];
_ifNull = list[4];
AddAutoLink(factory.Find(out _value, Update));
Update();
}
private void Update()
{
object value = _value.Value;
IConvertible asIConvertible = value as IConvertible;
if (null == asIConvertible)
Value = _ifNull;
else
try
{
double asDouble = asIConvertible.ToDouble(this);
if (asDouble > 0)
Value = _greater;
else if (asDouble == 0)
Value = _equal;
else
Value = _less;
}
catch
{
Value = _ifNull;
}
}
///
///
///
///
/// A factory for the value we want to monitor. The result should be IConvertible to double.
///
/// The value to return if the input is positive.
/// The value to return if the input is 0.
/// The value to return if the input is negative.
/// The value to return in case of error.
///
public static Factory GetFactory(Factory value, object greater,
object equal, object less, object ifNull = null)
{
return new FactoryWithHash(arg => new CompareToZeroDataNode(arg),
typeof(CompareToZeroDataNode),
StaticList.Create(value, greater, equal, less, ifNull));
}
public static bool TryDeconstruct(object factory, out Factory value, out object greater,
out object equal, out object less, out object ifNull)
{
value = null;
greater = null;
equal = null;
less = null;
ifNull = null;
try
{
FactoryWithHash f = factory as FactoryWithHash;
if (null == f)
return false;
if (!typeof(CompareToZeroDataNode).Equals(f.MetaData))
return false;
StaticList.Extract(f.Arguments, out value, out greater, out equal, out less, out ifNull);
return true;
}
catch
{ // It's tempting not to catch this exception. This is something that looks like one
// of our factories, but was malformed. This should be an assertion failure, maybe.
return false;
}
}
public static bool TryDeconstruct(object factory)
{
Factory f;
object o;
return TryDeconstruct(factory, out f, out o, out o, out o, out o);
}
object IFormatProvider.GetFormat(Type formatType)
{ // We don't want to do anything exotic. We want Int16 and UInt32, and other simple
// types to conert to double.
return null;
}
}
///
/// Concatinate N lists to make one list.
///
/// Each input DataNode should return an IEnumerable. (The non-generic type. Of course,
/// each specialized IEnumerable inherits from this.) If the result is null, we treat
/// that as an empty list. The input data nodes should not return any other data types.
/// Currently we naïvely treat any other data type as an empty list. But that could
/// change and we could fail an assertion or something like that. The input lists should
/// be read-only and thread safe.
///
/// This DataNode always returns a read-only IList(object). It never returns null.
///
/// We call object.Equals(object, object) on the items in the list. That call should
/// be thread-safe and reasonably cheap. Other than that restriction the elements of
/// the list can be anything, including null.
///
/// Note the current optimization strategy: Each time an input notifies us of a change
/// we build a new list that is a concatination of the inputs. Then we compare the
/// new result to the current result, one element at a time, to see if there was really
/// a change or not. By default DataNode.Value calls object.Equal(object, object) on
/// the top level objects only. We know that would always say there was a change because
/// we are creating a new list and List.Equals() only compares object identity. It doesn't
/// walk through the list.
///
public class ConcatinateListsDataNode : DataNode
{
private List _dataNodes;
private ConcatinateListsDataNode(object args)
{
StaticList list = (StaticList)args;
List dataNodes = new List(list.Length);
foreach (object item in list)
{
Factory factory = (Factory)item;
DataNode dataNode;
AddAutoLink(factory.Find(out dataNode, Update));
dataNodes.Add(dataNode);
}
Thread.MemoryBarrier();
_dataNodes = dataNodes;
Update();
}
private void Update()
{
if (null == _dataNodes)
// We have received a response before the constructor finished.
return;
// What if the inputs are updated in different threads? We might have two
// calls to Update at the same time. The second one might finish before the
// first, and the first would overwrite the newest result with an older
// result.
lock (_dataNodes)
{
List newValue = new List();
foreach (DataNode dataNode in _dataNodes)
{
IEnumerable list = dataNode.Value as IEnumerable;
if (null != list)
{
foreach (object item in list)
newValue.Add(item);
}
}
SetListValue(newValue);
}
}
public static Factory GetFactory(params Factory[] sublists)
{
return new FactoryWithHash(arg => new ConcatinateListsDataNode(arg),
typeof(ConcatinateListsDataNode), StaticList.Create(sublists));
}
}
///
/// Most data nodes are respononsible for subscribing to whatever data sources
/// they need. PushDataNode is different. By default the Value is always null
/// and there are no callbacks. However, someone holding onto this data node
/// can send any data they want. They can set the new value and a notification
/// will be sent to all listeners.
///
/// This was originally designed to work with the GUI. You can wrap a data
/// node around a text box or something like that. Any time the user types,
/// a callback will read the Text property, process it, and use the result
/// to update this data node's Value property.
///
public class PushDataNode : DataNode
{
private readonly object _args;
private PushDataNode(object args)
{
_args = args;
}
///
/// This is the traditional form of GetFactory(). For this class there are two other
/// forms of GetFactory() which might work better. This function is used by the other
/// two.
///
/// See GetFactory() with no arguments and Find(), both in this class.
///
///
/// Any unique value. Two calls to GetFactory with the same key will return the same
/// data node. Two calls with different keys will return different data nodes.
///
/// This can be null or any object which respects GetHashCode() and Equals().
///
/// The factory.
public static Factory GetFactory(object key)
{
return new FactoryWithHash(arg => new PushDataNode(arg), typeof(PushDataNode), key);
}
///
/// Typically a form will use Find() to get the first copy of this object. It will
/// hold on to this object so it can broadcast messages. It will call this method
/// on the object to create factories. Those factories will allow someone to listen
/// to the broadcast messages.
///
/// A factory which will point back to this exact object.
public Factory GetFactory()
{
return GetFactory(_args);
}
///
/// Typically a form will use this method to get the first copy of this object.
/// It will hold on to thos object so it can broadcast messages. It will call
/// GetFactory() on this object to create factories. Those factories will allow
/// someone to listen to the broadcast messages.
///
/// This also takes care of converting from DataNode to PushDataNode. You could
/// always do that yourself, this is just a convenience. Normally no one needs
/// to make that conversion. Normally you only need access to the Value property.
/// But that's for listeners. This call is good for broadcasters.
///
///
/// Any unique value. Two calls to Find() with the same key will return the same
/// data node. Two calls with different keys will return different data nodes.
///
/// This can be null or any object which respects GetHashCode() and Equals().
///
/// This will receive a copy of the data node.
/// A standard DataNode.Link
public static Link Find(object key, out PushDataNode dataNode)
{
DataNode temp;
Link result = GetFactory(key).Find(out temp);
dataNode = (PushDataNode)temp;
return result;
}
///
/// This changes the Value property and notifies all listeners.
///
/// The value for the Value property.
public void SetValue(object newValue)
{
Value = newValue;
}
///
/// If you only set the value to null or to values of type IList(T), this might
/// be an optimization.
///
/// See DataNode.SetListValue() for more details. This is the same function, but
/// changed from protected to public.
///
///
/// All values must be null or of type IList(T). T cannot change for the lifetime
/// of this object.
///
/// The new value that we want to broadcast.
public new void SetListValue(IList newList)
{
base.SetListValue(newList);
}
}
public static class DataNodeExtensionMethods
{
public static DataNode.Factory SetSymbol(this DataNode.Factory original, string symbol)
{
return (DataNode.Factory)original.ReplaceWith(new Dictionary() { {DataNode.PlaceHolder.SYMBOL.Key, symbol} });
}
public static DataNode.Factory ReplaceWithF(this DataNode.Factory original, Dictionary replacements)
{
return (DataNode.Factory)original.ReplaceWith(replacements);
}
public static DataNode.Factory StringToDouble(this DataNode.Factory original)
{
return TransformationDataNode.GetFactory(TransformationDataNode.StringToDouble, original);
}
public static DataNode.Factory StringToInt(this DataNode.Factory original)
{
return TransformationDataNode.GetFactory(TransformationDataNode.StringToInt, original);
}
};
}