import 'dart:core'; import '../Data/DC.dart'; import '../Data/Structure.dart'; import '../Data/AutoList.dart'; import './IStore.dart'; import './IResource.dart'; import '../Data/KeyList.dart'; import './StorageMode.dart'; import '../Data/ValueObject.dart'; import '../Core/IEventHandler.dart'; import '../Security/Permissions/Ruling.dart'; import '../Security/Permissions/IPermissionsManager.dart'; import '../Security/Permissions/ActionType.dart'; import './Template/ResourceTemplate.dart'; import './Template/PropertyTemplate.dart'; import './Template/FunctionTemplate.dart'; import './Template/EventTemplate.dart'; import '../Security/Authority/Session.dart'; import './Template/MemberTemplate.dart'; import '../Data/PropertyValue.dart'; import 'Warehouse.dart'; class Instance extends IEventHandler { String _name; AutoList _children; IResource _resource; IStore _store; AutoList _parents; //bool inherit; ResourceTemplate _template; AutoList _managers; KeyList _attributes; List _ages = new List(); List _modificationDates = new List(); int _instanceAge; DateTime _instanceModificationDate; int _id; /// /// Instance attributes are custom properties associated with the instance, a place to store information by IStore. /// KeyList get attributes => _attributes; @override String toString() => _name + " (" + link + ")"; bool removeAttributes([List attributes = null]) { if (attributes == null) this._attributes.clear(); else { for (var attr in attributes) this.attributes.remove(attr); } return true; } Structure getAttributes([List attributes = null]) { var st = new Structure(); if (attributes == null) { var clone = this.attributes.keys.toList(); clone.add("managers"); attributes = clone.toList(); } for(var attr in attributes) { if (attr == "name") st["name"] = _name; else if (attr == "managers") { var mngrs = new List(); for (var i = 0; i < _managers.length; i++) { var mst = new Structure(); mst["type"] = _managers[i].runtimeType; mst["settings"] = _managers[i].settings; mngrs.add(mst); } st["managers"] = mngrs; } else if (attr == "parents") { st["parents"] = _parents.toList(); } else if (attr == "children") { st["children"] = _children.toList(); } else if (attr == "childrenCount") { st["childrenCount"] = _children.count; } else if (attr == "type") { st["type"] = resource.runtimeType; } else st[attr] = _attributes[attr]; } return st; } bool setAttributes(Structure attributes, [bool clearAttributes = false]) { try { if (clearAttributes) _attributes.clear(); for (var attrKey in attributes.keys) if (attrKey == "name") _name = attributes[attrKey]; else if (attrKey == "managers") { _managers.clear(); var mngrs = attributes[attrKey] as List; // this is not implemented now, Flutter doesn't support mirrors, needs a workaround @ Warehouse.registerManager /* for (var mngr in mngrs) { var m = mngr as Structure; var type = Type.GetType(m["type"] as string); if (Codec.implementsInterface(); foreach (var manager in managers) { var mngr = new Structure(); mngr["settings"] = manager.Settings; mngr["type"] = manager.GetType().FullName; mngrs.Add(mngr); } st["managers"] = mngrs; return st; }*/ /// /// Get the age of a given property index. /// /// Zero-based property index. /// Age. int getAge(int index) { if (index < _ages.length) return _ages[index]; else return 0; } /// /// Set the age of a property. /// /// Zero-based property index. /// Age. void setAge(int index, int value) { if (index < _ages.length) { _ages[index] = value; if (value > _instanceAge) _instanceAge = value; } } /// /// Set the modification date of a property. /// /// Zero-based property index. /// Modification date. void setModificationDate(int index, DateTime value) { if (index < _modificationDates.length) { _modificationDates[index] = value; if (_instanceModificationDate == null || value.millisecondsSinceEpoch > _instanceModificationDate.millisecondsSinceEpoch) _instanceModificationDate = value; } } /// /// Get modification date of a specific property. /// /// Zero-based property index /// Modification date. DateTime getModificationDate(int index) { if (index < _modificationDates.length) return _modificationDates[index]; else return new DateTime(0); } /// /// Load property value (used by stores) /// /// Property name /// Property age /// Property value /// bool loadProperty(String name, int age, DateTime modificationDate, dynamic value) { /* var pt = _template.getPropertyTemplate(name); if (pt == null) return false; if (pt.info.propertyType == typeof(DistributedPropertyContext)) return false; try { if (pt.into.canWrite) pt.info.setValue(resource, DC.CastConvert(value, pt.Info.PropertyType)); } catch(ex) { // } setAge(pt.index, age); setModificationDate(pt.index, modificationDate); */ return true; } /// /// Age of the instance, incremented by 1 in every modification. /// int get age => _instanceAge; // this must be internal set age (value) => _instanceAge = value; /// /// Last modification date. /// DateTime get modificationDate => _instanceModificationDate; /// /// Instance Id. /// int get id => _id; /// /// Import properties from bytes array. /// /// /// bool deserialize(List properties) { for (var i = 0; i < properties.length; i++) { var pt = _template.getPropertyTemplateByIndex(i); if (pt != null) { var pv = properties[i]; loadProperty(pt.name, pv.age, pv.date, pv.value); } } return true; } /// /// Export all properties with ResourceProperty attributed as bytes array. /// /// List serialize() { List props = new List(); for (var pt in _template.properties) { // var rt = pt.info.getValue(resource, null); // props.add(new PropertyValue(rt, _ages[pt.index], _modificationDates[pt.index])); } return props; } /* public bool Deserialize(byte[] data, uint offset, uint length) { var props = Codec.ParseValues(data, offset, length); Deserialize(props); return true; } */ /* public byte[] Serialize(bool includeLength = false, DistributedConnection sender = null) { //var bl = new BinaryList(); List props = new List(); foreach (var pt in template.Properties) { var pi = resource.GetType().GetProperty(pt.Name); var rt = pi.GetValue(resource, null); // this is a cool hack to let the property know the sender if (rt is Func) rt = (rt as Func)(sender); props.Add(rt); } if (includeLength) { return Codec.Compose(props.ToArray(), false); } else { var rt = Codec.Compose(props.ToArray(), false); return DC.Clip(rt, 4, (uint)(rt.Length - 4)); } } public byte[] StorageSerialize() { var props = new List(); foreach(var pt in template.Properties) { if (!pt.Storable) continue; var pi = resource.GetType().GetProperty(pt.Name); if (!pi.CanWrite) continue; var rt = pi.GetValue(resource, null); props.Add(rt); } return Codec.Compose(props.ToArray(), false); } */ /// /// If True, the instance can be stored to disk. /// /// bool isStorable() { return false; } void emitModification(PropertyTemplate pt, dynamic value) { _instanceAge++; var now = DateTime.now().toUtc(); _ages[pt.index] = _instanceAge; _modificationDates[pt.index] = now; if (pt.storage == StorageMode.NonVolatile) { _store.modify(_resource, pt.name, value, _ages[pt.index], now); } else if (pt.storage == StorageMode.Recordable) { _store.record(_resource, pt.name, value, _ages[pt.index], now); } emitArgs("resourceModified", [_resource, pt.name, value]); //_resource.emitArgs("modified", [pt.name, value]); _resource.emitArgs(":${pt.name}", [value]); } /// /// Notify listeners that a property was modified. /// /// /// /// modified(String propertyName) { var valueObject = new ValueObject(); if (getPropertyValue(propertyName, valueObject)) { var pt = _template.getPropertyTemplateByName(propertyName); emitModification(pt, valueObject.value); } } emitResourceEvent(issuer, List receivers, String name, dynamic args) { emitArgs("resourceEventOccurred", [_resource, issuer, receivers, name, args]); } /// /// Get the value of a given property by name. /// /// Property name /// Output value /// True, if the resource has the property. bool getPropertyValue(String name, ValueObject valueObject) { var pt = _template.getPropertyTemplateByName(name); /* if (pt != null && pt.info != null) { valueObject.value = pt.info.getValue(_resource, null); return true; }*/ valueObject.value = null; return false; } /* public bool Inherit { get { return inherit; } }*/ /// /// List of parents. /// AutoList get parents => _parents; /// /// Store responsible for creating and keeping the resource. /// IStore get store => _store; /// /// List of children. /// AutoList get children => _children; /// /// The unique and permanent link to the resource. /// String get link { if (_store != null) return _store.link(_resource); else { var l = new List(); var p = _resource; while (true) { l.insert(0, p.instance.name); if (p.instance.parents.count == 0) break; p = p.instance.parents.first(); } return l.join("/"); } } /// /// Instance name. /// String get name => _name; set name(value) => name = value; /// /// Resource managed by this instance. /// IResource get resource => _resource; /// /// Resource template describes the properties, functions and events of the resource. /// ResourceTemplate get template => _template; /// /// Check for permission. /// /// Caller sessions. /// Action type /// Function, property or event to check for permission. /// Permission inquirer. /// Ruling. Ruling applicable(Session session, ActionType action, MemberTemplate member, [dynamic inquirer = null]) { for(var i = 0; i < _managers.length; i++) { var r = _managers[i].applicable(this.resource, session, action, member, inquirer); if (r != Ruling.DontCare) return r; } return Ruling.DontCare; } /// /// Execution managers. /// AutoList get managers => _managers; /// /// Create new instance. /// /// Instance Id. /// Name of the instance. /// Resource to manage. /// Store responsible for the resource. Instance(int id, String name, IResource resource, IStore store, [ResourceTemplate customTemplate = null, int age = 0]) { _store = store; _resource = resource; _id = id; _name = name; _instanceAge = age; _attributes = new KeyList(this); _children = new AutoList(this); _parents = new AutoList(this); _managers = new AutoList(this); _children.on("add", children_OnAdd); _children.on("remove", children_OnRemoved); _parents.on("add", parents_OnAdd); _parents.on("remove", parents_OnRemoved); resource.on("destroy", resource_OnDestroy); if (customTemplate != null) _template = customTemplate; else _template = Warehouse.getTemplateByType(resource.runtimeType); // set ages for (int i = 0; i < _template.properties.length; i++) { _ages.add(0); _modificationDates.add(new DateTime(0));//DateTime.MinValue); } /* // connect events Type t = resource.runtimeType; var events = t.GetTypeInfo().GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); foreach (var evt in events) { //if (evt.EventHandlerType != typeof(ResourceEventHanlder)) // continue; if (evt.EventHandlerType == typeof(ResourceEventHanlder)) { var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true); if (ca.Length == 0) continue; ResourceEventHanlder proxyDelegate = (args) => EmitResourceEvent(null, null, evt.Name, args); evt.AddEventHandler(resource, proxyDelegate); } else if (evt.EventHandlerType == typeof(CustomResourceEventHanlder)) { var ca = (ResourceEvent[])evt.GetCustomAttributes(typeof(ResourceEvent), true); if (ca.Length == 0) continue; CustomResourceEventHanlder proxyDelegate = (issuer, receivers, args) => EmitResourceEvent(issuer, receivers, evt.Name, args); evt.AddEventHandler(resource, proxyDelegate); } } */ } void children_OnRemoved(Instance parent, IResource value) { value.instance.parents.remove(_resource); } void children_OnAdd(Instance parent, IResource value) { if (!value.instance.parents.contains(_resource)) value.instance.parents.add(_resource); } void parents_OnRemoved(Instance parent, IResource value) { value.instance.children.remove(_resource); } void parents_OnAdd(Instance parent, IResource value) { if (!value.instance.children.contains(_resource)) value.instance.children.add(_resource); } void resource_OnDestroy(sender) { emitArgs("resourceDestroyed", [sender]); } }