Error formatting macro: include: java.lang.IllegalArgumentException: No link could be created for 'TOOLS:Header, Design'.

See issue *

Unknown macro: {jiraissue}

*

In the first version of a DelftShell the following approach was implemented to manage different data of the project and models:

public enum DataItemRole { Input, Output, InputOutput, Debug }

public interface IDataItem
{
   int Id { get; set; }

   string Name { get; set; }
   string Description { get; set; }

   DataItemRole Role { get; set; }

   object Value { get; set; }
   Type ValueType { get; set; }

   object Owner { get; set; }

   IDataItem Parent { get; set; }
   IDataItem[] Children { get; }

   bool CanLinkTo(IDataItem child);

   event EventHandler Changed;
}

// NOTE: it is different from real implementation, interface is actually abstract class
//       additionally there are a lot of crappy attributes and workarounds to make XML serialization work.
//       it was simplified to concentrate only on a real content of the IDataItem

This interface is used to wrap various data items (stored in the Value property) and to add the following aspects to them:

  • Linking - Parent / Children / CanLinkTo
  • Identifiable - Id
  • Nameable - all data should have name when they are added to the model or project
  • Value Container - data item is composed with object of certain type
  • Sends event on changes - when data object inside data item has been changed - it will send an even
  • Role - defines how data supposed to be used
  • Owner - defines object containing this data item

However during implementation of HABITAT and Verkenner the following issues appeared:

  • We had to change interface to abstract class in order to XML serialize it
  • As result attributes had to be added to each property which made it less readable [XmlElement(...)], [XmlAttribute(...)].
  • All data objects were forced to implement IDataItem which made them a bit more complicated then they should be.
  • It becomes complicated for new developers to implement all these data item-related things in all their data classes.
  • Everything becomes too much coupled with the data items.

Data items are used mainly in the Model and Project. And also shared between different view and services for example while two data items are linked.

In order to improve architecture the following is proposed: decouple TimeSeries from DataItem - use composition instead of polymorphisms.

Instead of deriving new data types from IDataItem:

public class TimeSeries: IDataItem
{
   #region IDataItem Members
   ...
   #endregion

   #region TimeSeries Members
   ...
   #endregion
}

public TimeSeriesInterpolationModel: IModel
{
   private IList<IDataItem> data;

   public TimeSeriesInterpolationModel()
   {
      data = new List<IDataItem>();

      // time series implements IDataItem
      TimeSeries timeSeries = new TimeSeries();
      timeSeries.Name = "input time series;
      timeSeries.Role = DataItemRole.Input;

      data.Add(timeSeries);
   }
}

Use:

public class TimeSeries
{
   #region TimeSeries Members
   ...
   #endregion
}

public TimeSeriesInterpolationModel: IModel
{
   private IList<IDataItem> data;

   public TimeSeriesInterpolationModel()
   {
      data = new List<IDataItem>();

      // first make object itself
      TimeSeries timeSeries = new TimeSeries();

      // wrap it with DataItem
      DataItem timeSeriesDataItem = new DataItem(timeSeries);
      timeSeriesDataItem.Name = "input time series";
      timeSeriesDataItem.Role = DataItemRole.Input;

      data.Add(timeSeriesDataItem);
   }
}

Since we're still interested to link, name, etc. our TimeSeries object when it is contained in the model it is wrapped by a DataItem.

Even it sounds like an nice solution there are some issue which prevent to implement it easily. Aspects listed above still should be somehow coupled with a real data object.

Let's have a look at each of them and try to find out possible consequences when using new approach:

  • Linking - Parent / Children / CanLinkTo()

Will be available in DataItem class only, as result time series itself will never know where it is linked to. In general it means that Model will be responsible to handle linkage information and if certain data item is linked to another model - provide TimeSeries object from linked data item instead of it's own.

  • Identifiable - Id

Objects itself will not need to be identifiable unless they will need to be saved separately. For example when data are saved into a database. In this case there might be a table defined for a DataItem entity and also a separated table for a TimeSeries. In this case time series will need to be identified and also relation between DataItem and TimeSeries tables should be somehow handled (keep in mind that TimeSeries does not know about DataItem anomore).

  • Nameable - all data should have name when they are added to the model or project

If data contained in the data item is simple, e.g. just an integer value or string - there is not problem. When underlying data object is more complicated, for example TimeSeries which may have a Name property by itself - it should be somehow redirected to IDataItem.Name property. Probably it is possible to do automatically based on underlying object properties: IDataItem may check properties of the contained object and if there is a property called Name - use it when someone else calls IDataItem.Name.

  • Value Container - data item is composed with object of certain type

Data object contained in the data item will become a Value.

  • Sends event on changes - when data object inside data item has been changed - it will send an event

Probably we can handle this using some attribute-based approach, e.g.:

public class MyCustomDataType
{
   [TrackChanges]
   public int MyValue { get {...} set {...} }
}

In this case DataItem class which wraps MyCustomDataType may attach to all changes in the MyValue property and then even can be redirected to a DataItem.

Update: looks like in C# attributes mean only meta information. It should be possible to use above approach if:

  • properties will be virtual
  • objects will be created using custom factory which will generate object with override properties using AOP techniques, like in: RunInUIThread attribute example from Roy Osherove.

A bit more ugly but working solution can be using INotifyPropertyChanged interface.

  • Role - defines how data supposed to be used

Those who need to know about Role of the data will need to query DataItem by their data object and then theck role there. Probably it makes sense to make IList<IDataItem> DataItems property of the IModel a Dictionary instead of IList. Another possibility is to handle it somewhere in IDataItemService:

interface IDataItemService 
{
   IDataItem GetDataItem(object o);
}
  • Owner - defines object containing this data item

Is not an issue if previous item is solved.

  • No labels