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/TypeTemplate.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; late AutoList _children; IResource _resource; IStore? _store; late AutoList _parents; //bool inherit; late TypeTemplate _template; late AutoList _managers; late KeyList _attributes; List _ages = []; List _modificationDates = []; 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 = []; 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] as String; 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 as DateTime).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 = []; 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]); _resource.emitProperty(pt.name); } /// /// Notify listeners that a property was modified. /// /// /// /// modified(String propertyName) { var valueObject = new ValueObject(); if (getPropertyValue(propertyName, valueObject)) { var pt = _template.getPropertyTemplateByName(propertyName); if (pt != null) 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 = []; var p = _resource; while (true) { if (p.instance != null) break; var pi = p.instance as Instance; l.insert(0, pi.name); if (pi.parents.count == 0) break; p = pi.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. /// TypeTemplate 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(this._id, this._name, this._resource, this._store, [TypeTemplate? customTemplate = null, this._instanceAge = 0]) { _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 != null) { var ins = value.instance as Instance; if (ins.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 != null) { var ins = value.instance as Instance; if (!ins.children.contains(_resource)) value.instance?.children.add(_resource); } } void resource_OnDestroy(sender) { emitArgs("resourceDestroyed", [sender]); } }