3636
3737% Todo: Validation.
3838% - Linked subject states should have same subject
39+ % - Add method to update links for specified instances
3940
4041% Need mechanism to check if embedded nodes are added to the collection
4142
5152 NumNodes
5253 NumTypes
5354 end
54-
55+
5556 properties (SetAccess = protected )
5657 % Nodes - Dictionary storing instances as values with identifiers
5758 % as keys
6667
6768 properties (SetAccess = protected )
6869 LinkResolver
69-
70+
7071 % MetadataStore - Optional metadata store for saving/loading
7172 MetadataStore openminds.interface.MetadataStore = openminds.internal.FileMetadataStore.empty
7273 end
73-
74+
7475 methods % Constructor
7576 function obj = Collection(instance , options )
7677 % Create an instance of an openMINDS collection
9798 % creates a collection with the specified metadata store. If no
9899 % instances are provided, the collection will automatically load
99100 % instances from the store.
100-
101+
101102 % collection = openminds.Collection(..., NameA, ValueA, ... )
102103 % also specifies optional name value pairs when creating the
103104 % collection.
106107 % - Name : A name for the collection
107108 % - Description : A description of the collection
108109 % - MetadataStore : A metadata store for saving/loading instances
109-
110+
110111 arguments (Repeating )
111112 instance % openminds.abstract.Schema
112113 end
126127 obj.Nodes = containers .Map ;
127128 obj.TypeMap = containers .Map ;
128129 end
129-
130+
130131 obj .initializeFromInstances(instance )
131132
132133 obj.Name = options .Name ;
133134 obj.Description = options .Description ;
134135 obj.MetadataStore = options .MetadataStore ;
135-
136+
136137 % Auto-load from MetadataStore if provided and no instances given
137138 if isempty(instance ) && ~isempty(obj .MetadataStore )
138139 obj .load();
148149 numNodes = length(obj .Nodes );
149150 end
150151 end
151-
152+
152153 function numTypes = get .NumTypes(obj )
153154 if isa(obj .TypeMap , ' dictionary' )
154155 numTypes = numEntries(obj .TypeMap );
@@ -190,16 +191,19 @@ function add(obj, instance, options)
190191 end
191192 arguments
192193 options.AddSubNodesOnly = false ;
194+ options.AbortIfNodeExists = true ;
193195 end
194196
195197 for i = 1 : numel(instance )
196198 thisInstance = instance{i };
197199 for j = 1 : numel(thisInstance ) % If thisInstance is an array
198- obj .addNode(thisInstance(j ), " AddSubNodesOnly" , options .AddSubNodesOnly );
200+ obj .addNode(thisInstance(j ), ...
201+ " AddSubNodesOnly" , options .AddSubNodesOnly , ...
202+ " AbortIfNodeExists" , options .AbortIfNodeExists );
199203 end
200204 end
201205 end
202-
206+
203207 function tf = contains(obj , instance )
204208 % Todo:work for arrays
205209 tf = false ;
@@ -210,10 +214,10 @@ function add(obj, instance, options)
210214 end
211215 end
212216 end
213-
217+
214218 function remove(obj , instance )
215219 % remove - Remove metadata instance from the collection
216-
220+
217221 if isstring(instance ) || ischar(instance )
218222 instanceId = instance ;
219223 elseif openminds .utility .isInstance(instance )
@@ -250,11 +254,11 @@ function remove(obj, instance)
250254 instance = instance{1 };
251255 end
252256 end
253-
257+
254258 function instances = getAll(obj )
255259 % getAll - Get all instances of collection
256260 instances = obj .Nodes .values();
257-
261+
258262 % For older MATLAB releases, the instances might be nested a
259263 % cell array, need to unnest if that's the case:
260264 if iscell(instances{1 })
@@ -269,11 +273,11 @@ function remove(obj, instance)
269273 end
270274
271275 tf = false ;
272-
276+
273277 if obj .NumNodes == 0
274278 return
275279 end
276-
280+
277281 typeKeys = obj .TypeMap .keys ;
278282 tf = any( endsWith(typeKeys , " ." +type ) ); % i.e ".Person"
279283 end
@@ -293,10 +297,10 @@ function remove(obj, instance)
293297 if obj .NumNodes == 0
294298 return
295299 end
296-
300+
297301 instanceKeys = obj .getInstanceKeysForType(type );
298302 if isempty(instanceKeys ); return ; end
299-
303+
300304 if isa(obj .Nodes , ' dictionary' )
301305 instances = obj .Nodes(instanceKeys );
302306 else
@@ -327,6 +331,10 @@ function updateLinks(obj)
327331 allInstances = [allInstances{: }];
328332 end
329333
334+ if iscolumn(allInstances )
335+ allInstances = reshape(allInstances , 1 , []);
336+ end
337+
330338 for instance = allInstances
331339 obj .addNode(instance{1 }, ...
332340 ' AddSubNodesOnly' , true , ...
@@ -360,22 +368,22 @@ function updateLinks(obj)
360368 % ------
361369 %
362370 % outputPaths (cell): A list of the file paths created.
363-
371+
364372 arguments
365373 obj openminds.Collection
366374 savePath (1 ,1 ) string = " "
367375 options.MetadataStore openminds.interface.MetadataStore = openminds.internal.FileMetadataStore.empty
368376 % options.SaveFormat = "jsonld" Implement if more formats are supported
369377 end
370-
378+
371379 % Update links before saving
372380 obj .updateLinks()
373381 instances = obj .getAll();
374382
375383 if savePath ~= " "
376384 tempStore = openminds .internal .store .createTemporaryStore(savePath );
377385 outputPaths = tempStore .save(instances );
378-
386+
379387 elseif ~isempty(options .MetadataStore )
380388 outputPaths = obj .MetadataStore .save(instances );
381389
@@ -387,7 +395,7 @@ function updateLinks(obj)
387395 error(' openminds:Collection:NoSavePath' , ...
388396 ' Either provide savePath or configure a MetadataStore' );
389397 end
390-
398+
391399 if ~nargout
392400 clear outputPaths
393401 end
@@ -436,7 +444,7 @@ function load(obj, loadPath, options)
436444 error(' openminds:Collection:PathNotFound' , ' Path not found: %s ' , loadPath );
437445 end
438446 end
439-
447+
440448 for i = 1 : numel(instances )
441449 if openminds .utility .isInstance(instances{i })
442450 obj .addNode(instances{i });
@@ -469,13 +477,13 @@ function load(obj, loadPath, options)
469477 % --------
470478 % collection : openminds.Collection
471479 % A new collection loaded with instances from the store
472-
480+
473481 arguments
474482 metadataStore (1 ,1 ) openminds.interface.MetadataStore
475483 options.Name (1 ,1 ) string = " "
476484 options.Description (1 ,1 ) string = " "
477485 end
478-
486+
479487 % Create collection with the metadata store
480488 collection = openminds .Collection(' MetadataStore' , metadataStore , ...
481489 ' Name' , options .Name , ' Description' , options .Description );
@@ -493,14 +501,18 @@ function load(obj, loadPath, options)
493501 end
494502
495503 wasAdded = false ;
496-
504+
497505 if isempty(instance .id )
498506 instance.id = obj .getBlankNodeIdentifier();
499507 end
500508
501- % Do not add openminds controlled term instances
502- if startsWith(instance .id , " https://openminds.ebrains.eu/instances/" )
503- return
509+ % Do not add openminds controlled term instances if disabled in
510+ % preferences
511+ if startsWith(instance .id , " https://openminds.ebrains.eu/instances/" ) ...
512+ || startsWith(instance .id , " https://openminds.om-i.org/instances/" )
513+ if ~openminds .getpref(' AddControlledInstanceToCollection' )
514+ return
515+ end
504516 end
505517
506518 if obj .NumNodes > 0
@@ -511,7 +523,10 @@ function load(obj, loadPath, options)
511523 end
512524 end
513525 end
514-
526+
527+ % Add subnodes first
528+ obj .addSubNodes(instance )
529+
515530 if ~options .AddSubNodesOnly
516531 obj .Nodes(instance .id ) = {instance };
517532 wasAdded = true ;
@@ -529,13 +544,12 @@ function load(obj, loadPath, options)
529544 obj .TypeMap(instanceType ) = {string(instance .id )};
530545 end
531546 end
532-
533- obj .addSubNodes(instance )
547+
534548 if ~nargout
535549 clear wasAdded
536550 end
537551 end
538-
552+
539553 % Add sub node instances (linked types) to the Node container.
540554 function addSubNodes(obj , instance )
541555 % Add links.
@@ -552,7 +566,7 @@ function addSubNodes(obj, instance)
552566 obj .addNode(embeddedInstances{i }, ' AddSubNodesOnly' , true );
553567 end
554568 end
555-
569+
556570 function identifier = getBlankNodeIdentifier(obj )
557571 fmt = ' _:%06d ' ;
558572 identifier = length(obj ) + 1 ;
@@ -567,19 +581,19 @@ function initializeFromInstances(obj, instance)
567581 isFilePath = @(x ) (ischar(x ) || isstring(x )) && isfile(x );
568582 isFolderPath = @(x ) (ischar(x ) || isstring(x )) && isfolder(x );
569583 isMetadata = @(x ) openminds .utility .isInstance(x );
570-
584+
571585 % Initialize from file(s)
572586 if all( cellfun(isFilePath , instance ) )
573587 obj .load(instance{: })
574-
588+
575589 % Initialize from folder
576590 elseif all( cellfun(isFolderPath , instance ) )
577591 obj .load(instance{: })
578-
592+
579593 % Initialize from instance(s)
580594 elseif all( cellfun(isMetadata , instance ) )
581595 obj .add(instance{: });
582-
596+
583597 else
584598 ME = MException(...
585599 ' OPENMINDS_MATLAB:Collection:InvalidInstanceSpecification' , ...
@@ -597,7 +611,7 @@ function initializeFromInstances(obj, instance)
597611
598612 if obj .NumTypes > 0
599613 typeKeys = obj .TypeMap .keys ;
600-
614+
601615 isMatch = strcmp(typeKeys , instanceType .ClassName );
602616 if any(isMatch )
603617 if isa(obj .TypeMap , ' dictionary' )
@@ -615,9 +629,9 @@ function initializeFromInstances(obj, instance)
615629 instanceKeys = {};
616630 return
617631 end
618-
632+
619633 existingKeys = obj .Nodes .keys();
620-
634+
621635 % Sanity check, make sure all keys exist in Nodes dictionary
622636 assert( all( ismember( instanceKeys , existingKeys ) ), ...
623637 ' TypeMap has too many keys' )
0 commit comments