1+ classdef Item < handle & matlab .mixin .CustomDisplay
2+
3+ % Changes from bot.item.internal.abstract.Item:
4+ % - Simplified constructor
5+ % - Added method for assigning manifest
6+ % - Changed display of non-scalar objects
7+
8+ %% PROPERTIES
9+ properties (SetAccess = public )
10+ id ; % ID of this item
11+ info ; % Struct containing info about this item
12+ end
13+
14+ %% HIDDEN PROPERTIES
15+ properties (Hidden , Access = protected )
16+ manifest ; % Handle to pertinent manifest containing all available Items of this class
17+ end
18+
19+ properties (Abstract , Hidden , Access = public , Constant )
20+ DATASET (1 ,1 ) bot.item.internal.enum.Dataset
21+ DATASET_TYPE (1 ,1 ) bot.item.internal.enum.DatasetType
22+ ITEM_TYPE (1 ,1 ) bot.item.internal.enum.ItemType
23+ end
24+
25+ properties (Abstract , SetAccess = protected , Hidden )
26+ CORE_PROPERTIES (1 ,: ) string ;
27+ LINKED_ITEM_PROPERTIES (1 ,: ) string ;
28+ end
29+
30+ properties (Hidden , SetAccess = protected , GetAccess = protected )
31+ ITEM_INFO_VALUE_PROPERTIES (1 ,: ) string = string.empty(1 ,0 );
32+ LINKED_ITEM_VALUE_PROPERTIES (1 ,: ) string = string.empty(1 ,0 );
33+ end
34+
35+ %% CONSTRUCTOR
36+ methods
37+
38+ function obj = Item(itemIDSpec )
39+
40+ arguments
41+ itemIDSpec {bot .item .internal .abstract .Item .mustBeItemIDSpec } = [];
42+ end
43+
44+ % No Input Argument Constructor Requirement
45+ if nargin == 0 || isempty(itemIDSpec )
46+ return ;
47+ end
48+
49+ numItems = countItems(itemIDSpec ); % Local function;
50+
51+ if numItems > 1
52+ itemClass = class(obj );
53+ obj(numItems ) = feval(itemClass );
54+
55+ for idx = 1 : numItems
56+ if istable(itemIDSpec )
57+ obj(idx ) = feval(itemClass , itemIDSpec(idx , : ));
58+ else
59+ obj(idx ) = feval(itemClass , itemIDSpec(idx ));
60+ end
61+ end
62+ return
63+ end
64+
65+ obj .assignManifest()
66+
67+ % Identify the manifest table row(s) associated to itemIDSpec
68+ if istable(itemIDSpec )
69+ manifestTableRow = itemIDSpec ;
70+ elseif isnumeric(itemIDSpec )
71+ manifestTableRow = obj .findManifestTableRow(itemIDSpec );
72+ else
73+ assert(false );
74+ end
75+
76+ assert(~isempty(manifestTableRow ), " BOT:Item:idNotFound" , ...
77+ " Specified numeric ID not found within manifest(s) of all available Items of class %s" , mfilename(' class' ));
78+
79+ % - Assign the table data to the metadata structure
80+ obj.info = table2struct(manifestTableRow );
81+ obj.id = obj .info .id ;
82+
83+ obj .initLinkedItems()
84+ end
85+ end
86+
87+ methods (Access = protected )
88+ function initLinkedItems(obj )
89+ % Subclasses may implement
90+ end
91+
92+ function str = getItemAnnotation(obj )
93+ % getItemAnnotation - Provide a custom item annotation for display
94+ % purposes.
95+ str = missing ;
96+ end
97+ end
98+
99+ methods (Access = private )
100+
101+ function assignManifest(obj )
102+ % assignManifest - Assign item manifest for specified object.
103+ datasetName = obj .DATASET ;
104+ datasetType = obj .DATASET_TYPE ;
105+
106+ if datasetName == bot .item .internal .enum .Dataset .VisualBehavior
107+ manifestClassName = sprintf(" %s%sManifest" , datasetName , datasetType );
108+ fullManifestClassName = sprintf(' bot.internal.metadata.%s .instance' , manifestClassName );
109+ elseif datasetName == bot .item .internal .enum .Dataset .VisualCoding
110+ manifestClassName = sprintf(" %sManifest" , datasetType );
111+ fullManifestClassName = sprintf(' bot.item.internal.%s .instance' , manifestClassName );
112+ end
113+
114+ obj.manifest = feval(fullManifestClassName );
115+ end
116+ end
117+
118+ methods
119+ function datasetType = getDatasetType(obj )
120+ datasetType = char(obj .DATASET_TYPE );
121+ end
122+
123+ function datasetName = getDatasetName(obj )
124+ datasetName = char(obj .DATASET );
125+ end
126+ end
127+
128+ methods (Access = protected ) % Subclasses may override
129+ function tableRow = findManifestTableRow(obj , itemId )
130+
131+ itemId = uint32(round(itemId )); % Ensure ID is correct type
132+
133+ manifestTable = obj .manifest .getItemTable(obj .ITEM_TYPE );
134+ matchingRow = manifestTable .id == itemId ;
135+ tableRow = manifestTable(matchingRow , : );
136+ end
137+ end
138+
139+ %% HIDDEN METHODS SUPERCLASS IMPLEMENTATION (matlab.mixin.CustomDisplay)
140+ methods (Hidden , Access = protected )
141+
142+ function str = getHeader(obj )
143+ 144+ str = replace(str , ' with properties' , sprintf(' (%s ) with properties' , obj(1 ).getDatasetName()));
145+ end
146+
147+ function groups = getPropertyGroups(obj )
148+ if ~isscalar(obj )
149+ 150+ else
151+
152+ % Core properties
153+ mc = metaclass(obj );
154+ dcs = [mc .PropertyList .DefiningClass ];
155+ corePropsLocal = findobj(mc .PropertyList(string({dcs .Name }) == mfilename(' class' )),' GetAccess' ,' public' ,' -and' ,' Hidden' ,false );
156+ groups(1 ) = matlab .mixin .util .PropertyGroup([corePropsLocal .Name obj .CORE_PROPERTIES ]);
157+
158+ % Derived properties from Info
159+ if ~isempty(obj .ITEM_INFO_VALUE_PROPERTIES )
160+ groups(end + 1 ) = matlab .mixin .util .PropertyGroup(obj .ITEM_INFO_VALUE_PROPERTIES , ' Info Derived Values' );
161+ end
162+
163+ % Linked item tables
164+ groups(end + 1 ) = matlab .mixin .util .PropertyGroup(obj .LINKED_ITEM_PROPERTIES , ' Linked Items' );
165+
166+ % Derived properties from Linked Item Tables
167+ if ~isempty(obj .LINKED_ITEM_VALUE_PROPERTIES )
168+ groups(end + 1 ) = matlab .mixin .util .PropertyGroup(obj .LINKED_ITEM_VALUE_PROPERTIES , ' Linked Item Derived Values' );
169+ end
170+ end
171+ end
172+
173+ function displayNonScalarObject(obj )
174+ % TODO: Refactor to use String, if keeping this nonscalar display format
175+
176+ % - Only display limited data
177+ % arr_size = size(obj);
178+ % size_str = sprintf("%d×", arr_size(1:end-1)) + sprintf("%d", arr_size(end));
179+ %
180+ % class_name = strsplit(class(obj), '.');
181+ % class_name = class_name{end};
182+ % class_name_part = sprintf('<a href="matlab:helpPopup %s">%s</a>', class(obj), class_name);
183+ %
184+ % fprintf(" %s %s array\n", size_str, class_name_part);
185+ %
186+ % ids_part = "[" + sprintf('%d, ', [obj(1:end-1).id]) + sprintf('%d]', obj(end).id);
187+ %
188+ % fprintf(' ids: %s\n', ids_part);
189+
190+ numObjects = numel(obj );
191+ stringRep = cell(1 , numObjects );
192+
193+ for i = 1 : numObjects
194+ stringRep{i } = sprintf(' %s (%d ) of type "%s "' , ...
195+ string(obj(i ).ITEM_TYPE), obj(i ).id, obj(i ).SessionType);
196+ % Todo: Is session type available for cells, probes etc?
197+
198+ annotation = obj(i ).getItemAnnotation();
199+ if ~ismissing(annotation )
200+ stringRep{i } = sprintf(' %s [%s ]' , stringRep{i }, annotation );
201+ end
202+ end
203+
204+ str = obj .getHeader ;
205+ str = strrep(str , ' with properties:' , ' ' );
206+ disp(str )
207+ fprintf( ' %s\n\n ' , strjoin(stringRep , ' \n ' ) );
208+ end
209+ end
210+
211+ %% HIDDEN METHODS - STATIC
212+ methods (Hidden , Static )
213+ function mustBeItemIDSpec(val )
214+ % MUSTBEITEMIDSPEC Validation function for items specified to BOT item factory functions for item object array construction
215+
216+ eidTypePrefix = " mustBeBOTItemId:" ;
217+ eidTypeSuffix = " " ;
218+ msgType = " " ;
219+
220+ if istable(val )
221+
222+ eidTypeSuffix = " invalidItemTable" ;
223+
224+ if ~any(ismember(val .Properties .VariableNames , ' id' )) && ~any(ismember(val .Properties .VariableNames , ' behavior_session_id' ))
225+ msgType = " Table supplied not recognized as a valid BOT Item information table" ;
226+ end
227+
228+ % if height(val) ~= 1
229+ % msgType = "Table supplied must have one and only one row";
230+ % end
231+
232+ elseif ~isnumeric(val ) || ~isvector(val ) || ~all(isfinite(val )) || any(val <= 0 )
233+ eidTypeSuffix = " invalidItemIDs" ;
234+ msgType = " Must specify BOT item object(s) to create with either a numeric vector of valid ID values or a valid Item information table" ;
235+ elseif ~isinteger(val ) && ~all(round(val )==val )
236+ eidTypeSuffix = " invalidItemIDs" ;
237+ msgType = " Must specify BOT item object(s) to create with either a numeric vector of valid ID values or a valid Item information table" ;
238+ end
239+
240+
241+ % Throw error
242+ if strlength(msgType ) > 0
243+ throwAsCaller(MException(eidTypePrefix + eidTypeSuffix ,msgType ));
244+ end
245+ end
246+
247+ function tf = isItemIDSpecScalar(itemIDSpec )
248+ if istable(itemIDSpec )
249+ tf = height(itemIDSpec ) == 1 ;
250+ else
251+ tf = numel(itemIDSpec ) == 1 ;
252+ end
253+ end
254+ end
255+
256+ % methods (Static)
257+ %
258+ % function tbl = removeUnusedCategories(tbl)
259+ % % TODO: Consider if it's a desired behavior for category lists to be narrowed for linked item tables? Or better to retain the "global" view of all available in the container session?
260+ %
261+ % if isempty(tbl)
262+ % return;
263+ % end
264+ %
265+ % varTypes = string(cellfun(@class,table2cell(tbl(1,:)),'UniformOutput',false));
266+ % varNames = string(tbl.Properties.VariableNames);
267+ %
268+ % catVarIdxs = find(varTypes.matches("categorical"));
269+ %
270+ % for idx = catVarIdxs
271+ % varName = varNames(idx);
272+ %
273+ % validCats = unique(tbl.(varName));
274+ % allCats = categories(tbl{1,varName});
275+ % invalidCats = setdiff(allCats,validCats);
276+ %
277+ % tbl.(varName) = removecats(tbl.(varName),invalidCats);
278+ %
279+ % end
280+ %
281+ % end
282+ % end
283+
284+ end
285+
286+ function nItems = countItems(itemIDSpec )
287+ if istable(itemIDSpec )
288+ nItems = height(itemIDSpec );
289+ else
290+ nItems = numel(itemIDSpec );
291+ end
292+ end
0 commit comments