diff --git a/+file/Attribute.m b/+file/Attribute.m index e86faf39..ae2a3a11 100644 --- a/+file/Attribute.m +++ b/+file/Attribute.m @@ -1,11 +1,12 @@ classdef Attribute < handle - properties(SetAccess=private) + properties name; %attribute key doc; %doc string required; %bool regarding whether or not this Attribute is required for class value; %Value readonly; %determines whether value can be changed or not dtype; %type of value + dependent; %set externally. If the attribute is actually dependent on an untyped dataset/group end methods @@ -17,10 +18,12 @@ obj.value = []; obj.readonly = false; obj.dtype = []; + obj.dependent = ''; if nargin < 1 return; end + %source is a java.util.HashMap obj.name = source.get('name'); obj.doc = source.get('doc'); diff --git a/+file/Dataset.m b/+file/Dataset.m index 8460d087..c82a8844 100644 --- a/+file/Dataset.m +++ b/+file/Dataset.m @@ -1,5 +1,5 @@ classdef Dataset < handle - properties(SetAccess=private) + properties name; doc; type; @@ -82,6 +82,9 @@ attriter = attributes.iterator(); for i=1:len nextattr = file.Attribute(attriter.next()); + if isempty(obj.type) + nextattr.dependent = obj.name; + end obj.attributes.(nextattr.name) = nextattr; end end @@ -100,12 +103,10 @@ return; end - if isempty(obj.name) - propname = obj.type; - else + if ~isempty(obj.name) propname = obj.name; + props(propname) = obj; end - props(propname) = obj; %there are only two classes that do this and they're all under %ecephys: ElectrodeTable and ElectrodeTableRegion. diff --git a/+file/Group.m b/+file/Group.m index 2a2b36f9..8e6d74be 100644 --- a/+file/Group.m +++ b/+file/Group.m @@ -1,5 +1,5 @@ classdef Group < handle - properties(SetAccess=private) + properties doc; name; canRename; @@ -88,6 +88,9 @@ attriter = attributes.iterator(); for i=1:len nextattr = file.Attribute(attriter.next()); + if isempty(obj.type) + nextattr.dependent = obj.name; + end obj.attributes.(nextattr.name) = nextattr; end end @@ -100,10 +103,11 @@ datasetiter = datasets.iterator(); obj.datasets = repmat(file.Dataset, len, 1); for i=1:len - obj.datasets(i) = file.Dataset(datasetiter.next()); - if isempty(obj.datasets(i).name) + ds = file.Dataset(datasetiter.next()); + if isempty(ds.name) anonDataCnt = anonDataCnt + 1; end + obj.datasets(i) = ds; end end @@ -115,10 +119,11 @@ subgroupiter = subgroups.iterator(); obj.subgroups = repmat(file.Group, len, 1); for i=1:len - obj.subgroups(i) = file.Group(subgroupiter.next()); - if isempty(obj.subgroups(i).name) + sg = file.Group(subgroupiter.next()); + if isempty(sg.name) anonGroupCnt = anonGroupCnt + 1; end + obj.subgroups(i) = sg; end end @@ -149,16 +154,14 @@ props = containers.Map; varargs = {}; - if ~isempty(obj.type) - propertyname = obj.type; - else + if ~isempty(obj.name) propertyname = obj.name; end if ~obj.elide if obj.isConstrainedSet varargs = {obj}; - else + elseif ~isempty(propertyname) props(propertyname) = obj; end end diff --git a/+file/fillClass.m b/+file/fillClass.m index c56741d3..9006a0ad 100644 --- a/+file/fillClass.m +++ b/+file/fillClass.m @@ -5,8 +5,35 @@ %% PROCESSING [processed, classprops, inherited] = processClass(name, namespace, pregen); class = processed(1); -validationlist = setdiff(keys(classprops.named), {name}); -propertylist = setdiff(validationlist, inherited); + +allprops = keys(classprops.named); +required = {}; +optional = {}; +readonly = {}; +defaults = {}; +%separate into readonly, required, and optional properties +for i=1:length(allprops) + pnm = allprops{i}; + prop = classprops.named(pnm); + + if ischar(prop) || isa(prop, 'java.util.HashMap') || isstruct(prop) || prop.required + required = [required {pnm}]; + else + optional = [optional {pnm}]; + end + + if isa(prop, 'file.Attribute') && prop.readonly + readonly = [readonly {pnm}]; + end + + if isa(prop, 'file.Attribute') && ~isempty(prop.value) + defaults = [defaults {pnm}]; + end +end +non_inherited = setdiff(allprops, inherited); +ro_unique = intersect(readonly, non_inherited); +req_unique = intersect(required, non_inherited); +opt_unique = intersect(optional, non_inherited); %% CLASSDEF if length(processed) <= 1 @@ -17,59 +44,38 @@ depnm = ['types.' pnamespace.name '.' parentname]; %WRITE end -%% PROPERTIES -%in format -> -ro_props = struct(); -req_props = struct(); -opt_props = struct(); -for i=1:length(propertylist) - propname = propertylist{i}; - prop = classprops.named(propname); - - if isa(prop, 'file.Attribute') && prop.readonly - ro_props.(propname) = prop.doc; - elseif ischar(prop) - req_props.(propname) = ['property of type ' prop]; - elseif isa(prop, 'java.util.HashMap') - req_props.(propname) = ['reference to type ' prop.get('target_type')]; - elseif isstruct(prop) - req_props.(propname) = ['table with properties {' strtrim(evalc('disp(fieldnames(prop)'')')) '}']; - elseif prop.required - req_props.(propname) = prop.doc; - else - opt_props.(propname) = prop.doc; - end -end - -%find all properties that contain hardcoded values or defaults -vals_with_prop = {}; -for i=1:length(validationlist) - vlname = validationlist{i}; - vprop = classprops.named(vlname); - if isa(vprop, 'file.Attribute') && ~isempty(vprop.value) - vals_with_prop = [vals_with_prop {vlname}]; - end -end - %% return classfile string classDef = [... 'classdef ' name ' < ' depnm newline... %header, dependencies '% ' name ' ' class.doc]; %name, docstr -propsDef = strjoin({... - file.fillProps(ro_props, 'READONLY', 'SetAccess=private')...%readonly properties - file.fillProps(req_props, 'REQUIRED')... %required properties - file.fillProps(opt_props, 'OPTIONAL')... %optional properties - }, newline); +propgroups = {... + @()file.fillProps(classprops.named, ro_unique, 'SetAccess=protected')... + @()file.fillProps(classprops.named, req_unique)... + @()file.fillProps(classprops.named, opt_unique)... + }; +docsep = {... + '% READONLY'... + '% REQUIRED'... + '% OPTIONAL'... + }; +propsDef = ''; +for i=1:length(propgroups) + pg = propgroups{i}; + pdef = pg(); + if ~isempty(pdef) + propsDef = [propsDef newline docsep{i} newline pdef]; + end +end + constructorBody = file.fillConstructor(name,... - namespace.name,... depnm,... - vals_with_prop,... %we need these values to determine if we can hardcode or not - [fieldnames(ro_props); fieldnames(req_props)]',... - fieldnames(opt_props)',... + defaults,... %all defaults, regardless of inheritance + req_unique,... + opt_unique,... classprops); -setterFcns = file.fillSetters(propertylist); -validatorFcns = file.fillValidators(validationlist, classprops, namespace); -exporterFcns = file.fillExport(name, propertylist, classprops); +setterFcns = file.fillSetters(setdiff(non_inherited, ro_unique)); +validatorFcns = file.fillValidators(allprops, classprops, namespace); +exporterFcns = file.fillExport(name, [req_unique opt_unique], classprops); methodBody = strjoin({constructorBody... '%% SETTERS' setterFcns... '%% VALIDATORS' validatorFcns... @@ -91,7 +97,7 @@ for i=length(branch):-1:1 node = branch(i); nodename = node.get('neurodata_type_def'); - + if ~isKey(pregen, nodename) if isgroup class = file.Group(node); diff --git a/+file/fillConstructor.m b/+file/fillConstructor.m index 9d1e8851..b6d10585 100644 --- a/+file/fillConstructor.m +++ b/+file/fillConstructor.m @@ -1,13 +1,12 @@ -function fcstr = fillConstructor(name, namespacename, parentname, pwithval, req_names, opt_names, props) +function fcstr = fillConstructor(name, parentname, defaults, required, optional, props) caps = upper(name); fcnbody = strjoin({['% ' caps ' Constructor for ' name]... ['% obj = ' caps '(parentname1,parentvalue1,..,parentvalueN,parentargN,name1,value1,...,nameN,valueN)']... }, newline); fcns = {... - @() fillParamDocs('REQUIRED', req_names, props.named)... - @() fillParamDocs('OPTIONAL', opt_names, props.named)... - @() fillSetDocs(name, props.varargs, namespacename)... - @() fillBody(parentname, pwithval, req_names, opt_names, props)... + @()fillParamDocs('REQUIRED', required, props.named)... + @()fillParamDocs('OPTIONAL', optional, props.named)... + @()fillBody(parentname, defaults, required, optional, props)... }; for i=1:length(fcns) fcn = fcns{i}; @@ -22,17 +21,6 @@ 'end'}, newline); end -function fcstr = fillSetDocs(name, varprops, namespace) -fcstr = ''; -for i=1:length(varprops) - nm = varprops{i}.type; - if strcmp(nm, name) - continue; - end - fcstr = [fcstr '% ' nm ' = list of types.' namespace '.' nm newline]; -end -end - function fdfp = fillDocFromProp(prop, propnm) if ischar(prop) fdfp = prop; @@ -86,59 +74,40 @@ end end -function bodystr = fillBody(pname, propwithvals, req_vars, opt_vars, props) -all_vars = [req_vars opt_vars]; - -upstream = {}; %kwargs to be sent to parent -hardcoded = {}; %hardcoded defaults that should be instantiated now. -for i=1:length(propwithvals) - pnm = propwithvals{i}; - prop = props.named(pnm); - if any(strcmp(all_vars, pnm)) %that is, it's noninherited - [~, status] = str2num(prop.value); - if status - wrapped_assgn = prop.value; - else - wrapped_assgn = ['''' prop.value '''']; - end - - hardcoded = [hardcoded {['obj.' pnm ' = ' wrapped_assgn ';']}]; - else - upstream = [upstream {pnm} {prop.value}]; - end -end -if isempty(upstream) +function bodystr = fillBody(pname, defaults, required, optional, props) +if isempty(defaults) bodystr = ''; else - bodystr = ['varargin = [' util.cellPrettyPrint(upstream) ' varargin];' newline]; + usmap = containers.Map; + for i=1:length(defaults) + nm = defaults{i}; + usmap(nm) = props.named(nm).value; + end + kwargs = io.map2kwargs(usmap); + bodystr = ['varargin = [' util.cellPrettyPrint(kwargs) ' varargin];' newline]; end bodystr = [bodystr 'obj = obj@' pname '(varargin{:});']; - -if ~isempty(hardcoded) - bodystr = [bodystr newline strjoin(hardcoded, newline)]; -end bodystr = strjoin({bodystr... 'p = inputParser;'... 'p.KeepUnmatched = true;'... %suppress subclass/parent props 'p.PartialMatching = false;'... 'p.StructExpand = false;'}, newline); - -for i=1:length(all_vars) - var = all_vars{i}; +params = [required optional]; +for i=1:length(params) + var = params{i}; bodystr = [bodystr newline 'addParameter(p, ''' var ''', []);']; end -req_empty_vars = setdiff(req_vars, propwithvals); %check required values that don't have a set value -req_vars_str = util.cellPrettyPrint(req_empty_vars); +req_unset = setdiff(required, defaults); %check required values that don't have a set value req_body = strjoin({... 'parse(p, varargin{:});'... - ['required = ' req_vars_str ';']... + ['required = ' util.cellPrettyPrint(req_unset) ';']... 'missing = intersect(p.UsingDefaults, required);'... 'if ~isempty(missing)'... ' error(''Missing Required Argument(s) { %s }'', strjoin(missing, '', ''));'... 'end'}, newline); bodystr = [bodystr newline req_body]; -for i=1:length(all_vars) - var = all_vars{i}; +for i=1:length(params) + var = params{i}; bodystr = [bodystr newline 'obj.' var ' = p.Results.' var ';']; end end \ No newline at end of file diff --git a/+file/fillProps.m b/+file/fillProps.m index 1c33c18a..91742424 100644 --- a/+file/fillProps.m +++ b/+file/fillProps.m @@ -1,16 +1,18 @@ -%propstruct is a struct with name->docstring -%options is a string containing prop attributes (i.e. Access=private) -%s is the string output -function s = fillProps(propstruct, propertydoc, options) -propnames = fieldnames(propstruct); -if isempty(propnames) - s = ''; - return; -end -proplines = cell(size(propnames)); -for i=1:length(propnames) - pnm = propnames{i}; - proplines{i} = [' ' pnm '; % ' propstruct.(pnm)]; +function s = fillProps(props, names, options) +proplines = cell(size(names)); +for i=1:length(names) + pnm = names{i}; + p = props(pnm); + if ischar(p) + doc = ['property of type ' p]; + elseif isa(p, 'java.util.HashMap') + doc = ['reference to type ' p.get('target_type')]; + elseif isstruct(p) + doc = ['table with properties ' util.cellPrettyPrint(fieldnames(p)')]; + else + doc = p.doc; + end + proplines{i} = [pnm '; % ' doc]; end if nargin >= 3 @@ -19,15 +21,8 @@ opt = ''; end -if nargin >= 2 - pd = ['% ' propertydoc]; -else - pd = ''; -end - s = strjoin({... - pd... ['properties' opt]... - strjoin(proplines, newline)... + file.addSpaces(strjoin(proplines, newline), 4)... 'end'}, newline); end \ No newline at end of file diff --git a/+file/writeDefStruct.m b/+file/writeDefStruct.m deleted file mode 100644 index f11b2425..00000000 --- a/+file/writeDefStruct.m +++ /dev/null @@ -1,17 +0,0 @@ -function writeDefStruct(fid, def_struct, svalue, varargin) -validateattributes(fid, {'double'}, {'scalar'}); -validateattributes(def_struct, {'struct'}, {'scalar'}); -validateattributes(svalue, {'string', 'char'}, {'scalartext'}); -p = inputParser; -p.addParameter('spaces', 0, @(x)validateattributes(x, {'numeric'}, {'scalar'})); -p.parse(varargin{:}); - -names = fieldnames(def_struct); -if ~isempty(names) - for i=1:length(names) - nm = names{i}; - fprintf(fid, repmat(' ', 1, p.Results.spaces)); - fprintf(fid, [svalue newline], nm, def_struct.(nm)); - end -end -end \ No newline at end of file diff --git a/+file/writeExportFunction.m b/+file/writeExportFunction.m deleted file mode 100644 index a5ea3dd0..00000000 --- a/+file/writeExportFunction.m +++ /dev/null @@ -1,25 +0,0 @@ -function writeExportFunction(fid, propType, nm, objnm, typenm, varargin) -validateattributes(fid, {'double'}, {'scalar'}); -validateattributes(propType, {'char', 'string'}, {'scalartext'}); -validateattributes(nm, {'char', 'string'}, {'scalartext'}); -validateattributes(objnm, {'char', 'string'}, {'scalartext'}); -validateattributes(typenm, {'char', 'string'}, {'scalartext'}); - -p = inputParser; -p.addParameter('spaces', 0); -p.addParameter('keepid', false); -p.addParameter('idname', 'loc_id'); -p.parse(varargin{:}); -fprintf(fid, file.spaces(p.Results.spaces)); -if p.Results.keepid - fprintf(fid, 'id = '); -end - -if any(strcmp(typenm, {'string', 'any'})) - fprintf(fid, ['h5util.write%s(%s, ''%s'', obj.%s'', ''%s'');' newline],... - propType, p.Results.idname, nm, objnm, typenm); -else - fprintf(fid, ['h5util.write%s(%s, ''%s'', %s(obj.%s)'', ''%s'');' newline],... - propType, p.Results.idname, nm, typenm, objnm, typenm); -end -end \ No newline at end of file diff --git a/+io/parseGroup.m b/+io/parseGroup.m index 96b03e56..9f1486a0 100644 --- a/+io/parseGroup.m +++ b/+io/parseGroup.m @@ -12,7 +12,12 @@ ds_info = info.Datasets(i); fp = [info.Name '/' ds_info.Name]; [ds, dsrefs] = io.parseDataset(filename, ds_info, fp); - props = [props; ds]; + if isa(ds, 'containers.Map') + props = [props; ds]; + else + props(ds_info.Name) = ds; + end + refs = [refs; dsrefs]; end @@ -36,11 +41,12 @@ keyboard; else %construct as kwargs and instantiate object - if isempty(root) %we are root parsed = types.core.NWBFile; return; end + kwargs = io.map2kwargs(props); + parsed = eval([typename '(kwargs{:})']); end end \ No newline at end of file diff --git a/+types/+util/checkDtype.m b/+types/+util/checkDtype.m index 6b9a7410..ae2fb694 100644 --- a/+types/+util/checkDtype.m +++ b/+types/+util/checkDtype.m @@ -2,6 +2,9 @@ function checkDtype(name, type, val) %ref %any, double, int/uint, char errmsg = ['Property `' name '` must be a ' type '.']; +if isempty(val) + return; +end switch type case 'double' if ~isnumeric(val) @@ -25,7 +28,7 @@ function checkDtype(name, type, val) end for i=1:length(val) subval = val{i}; - if ~isa(subval(1), type) + if ~isa(subval, type) error(errmsg); end end diff --git a/+util/cellPrettyPrint.m b/+util/cellPrettyPrint.m index e4332a2f..3218d302 100644 --- a/+util/cellPrettyPrint.m +++ b/+util/cellPrettyPrint.m @@ -2,8 +2,7 @@ s = ''; for i=1:length(val) v = val{i}; - [~, status] = str2num(v); - if status + if all(isstrprop(v, 'digit')) wrapped_v = v; else wrapped_v = ['''' v ''''];