Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions +file/+internal/isDescendantOf.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
function tf = isDescendantOf(name, namespace, targetAncestorName)
%isDescendantOf Check if a type inherits from an ancestor type.

tf = false;

if strcmp(name, targetAncestorName)
tf = true;
return
end

ancestry = namespace.getRootBranch(name);
for iAncestor = 1:length(ancestry)
parentRaw = ancestry{iAncestor};
typeDefIndex = isKey(parentRaw, namespace.TYPEDEF_KEYS);
currentAncestorName = parentRaw(namespace.TYPEDEF_KEYS{typeDefIndex});
tf = tf || strcmp(currentAncestorName, targetAncestorName);
end
end
49 changes: 47 additions & 2 deletions +file/fillClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
%namespace is the namespace context for this class

customBaseClasses = struct( ...
'DynamicTable', 'matnwb.neurodata.DynamicTableBase' ...
'DynamicTable', 'matnwb.neurodata.DynamicTableBase', ...
'AlignedDynamicTable', 'matnwb.neurodata.AlignedDynamicTableBase' ...
);

%% PROCESSING
Expand Down Expand Up @@ -191,8 +192,14 @@

fullMethodBody = strjoin({'methods' ...
file.addSpaces(methodBody, 4) 'end'}, newline);
schemaCategoryPropertyBlock = createPropertyBlockForAlignedDynamicTableCategories( ...
classprops, nonInherited, name, namespace);
readPolicyMethodBlock = fillReadPolicy(class, classprops);
classSections = {classDefinitionHeader, fullPropertyDefinition, fullMethodBody};
classSections = {classDefinitionHeader, fullPropertyDefinition};
if ~isempty(schemaCategoryPropertyBlock)
classSections{end+1} = schemaCategoryPropertyBlock;
end
classSections{end+1} = fullMethodBody;
if ~isempty(readPolicyMethodBlock)
classSections{end+1} = readPolicyMethodBlock;
end
Expand Down Expand Up @@ -245,6 +252,44 @@
'end'}, newline);
end

function propertyBlockStr = createPropertyBlockForAlignedDynamicTableCategories( ...
classProps, propertyNames, className, namespace)

propertyBlockStr = '';
if ~file.internal.isDescendantOf(className, namespace, 'AlignedDynamicTable')
return
end

categoryNames = string.empty(1, 0);
for iProperty = 1:length(propertyNames)
propertyName = propertyNames{iProperty};
propertyInfo = classProps(propertyName);
if isSchemaDefinedAlignedDynamicTableCategory(propertyInfo, namespace)
categoryNames(end+1) = string(propertyName); %#ok<AGROW>
end
end

formattedNames = """" + categoryNames + """";
if isempty(formattedNames)
propertyLine = 'DeclaredSchemaCategories = string.empty(1, 0);';
else
propertyLine = sprintf( ...
'DeclaredSchemaCategories = [%s];', strjoin(formattedNames, ', '));
end

propertyBlockStr = strjoin({ ...
'properties (Constant, Access = private)', ...
file.addSpaces(propertyLine, 4), ...
'end'}, newline);
end

function tf = isSchemaDefinedAlignedDynamicTableCategory(propertyInfo, namespace)
tf = isa(propertyInfo, 'file.Group') ...
&& ~propertyInfo.isConstrainedSet ...
&& ~isempty(propertyInfo.type) ...
&& file.internal.isDescendantOf(propertyInfo.type, namespace, 'DynamicTable');
end

function propertyBlockStr = createSchemaNameMappingBlock(schemaNames)
% Build a (Constant, Hidden) struct mapping the valid property identifier
% to its schema name for each reserved-keyword schema name.
Expand Down
4 changes: 3 additions & 1 deletion +file/fillConstructor.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
end

% Add custom validation for DynamicTable and its descendant classes
if file.isDynamicTableDescendant(name, namespace)
if file.internal.isDescendantOf(name, namespace, 'AlignedDynamicTable')
constructorElements{end+1} = ' obj.ensureAlignedTableConsistency();';
elseif file.internal.isDescendantOf(name, namespace, 'DynamicTable')
constructorElements{end+1} = ' obj.ensureDynamicTableConsistency();';
end

Expand Down
7 changes: 7 additions & 0 deletions +file/fillCustomConstraint.m
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@
' obj.ensureDynamicTableConsistency()\n', ...
'end'] );

case "AlignedDynamicTable"
customConstraintStr = sprintf( [...
'function checkCustomConstraint(obj)\n', ...
' checkCustomConstraint@types.untyped.MetaClass(obj)\n', ...
' obj.ensureAlignedTableConsistency()\n', ...
'end'] );

otherwise
customConstraintStr = '';
end
Expand Down
21 changes: 20 additions & 1 deletion +file/getPropertyHooks.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,23 @@
'val = types.util.dynamictable.validateColnames(val);' };
end

if file.isDynamicTableDescendant(typeName, namespace) ...
if strcmp(fullClassName, 'types.hdmf_common.AlignedDynamicTable') ...
&& strcmp(propName, 'categories')
hooks.ValidatorLines = { ...
'val = obj.validateCategoryNames(val);' };
end

if file.internal.isDescendantOf(typeName, namespace, 'DynamicTable') ...
&& isSchemaDefinedDynamicTableColumn(propName, prop)
hooks.PostsetStatements = { ...
sprintf('types.util.dynamictable.syncNamedColumn(obj, ''%s'');', propName) };
end

if file.internal.isDescendantOf(typeName, namespace, 'AlignedDynamicTable') ...
&& isSchemaDefinedAlignedDynamicTableCategory(prop, namespace)
hooks.PostsetStatements = [hooks.PostsetStatements, { ...
sprintf('obj.ensureCategoryNameRegistered(''%s'');', propName) }];
end
end

function tf = isSchemaDefinedDynamicTableColumn(propName, prop)
Expand All @@ -26,3 +38,10 @@
&& ~strcmp(propName, 'id') ...
&& ~endsWith(propName, '_index');
end

function tf = isSchemaDefinedAlignedDynamicTableCategory(prop, namespace)
tf = isa(prop, 'file.Group') ...
&& ~prop.isConstrainedSet ...
&& ~isempty(prop.type) ...
&& file.internal.isDescendantOf(prop.type, namespace, 'DynamicTable');
end
18 changes: 0 additions & 18 deletions +file/isDynamicTableDescendant.m

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
function result = collectConstantPropertiesAcrossHierarchy(className, propertyName)
% collectConstantPropertyAcrossHierarchy - Collect constant property values
% across class hierarchy
%
% Syntax:
% groupTypes = collectConstantPropertiesAcrossHierarchy(nwbTypeName)
% This function retrieves property names of unnamed groups associated with
% the specified NWB type name, traversing the class hierarchy to also include
% property names of unnamed groups for parent types.
%
% Input Arguments:
% nwbTypeName (1,1) string - The name of the NWB type for which property
% names of unnamed groups are to be retrieved.
%
% Output Arguments:
% groupPropertyNames - An array of property names of unnamed groups
% associated with the specified NWB type.
%
% Assumptions:
% 1. Class name is the name of a generated neurodata type
% 2. A parent neurodata type (superclass) is always defined as the first
% superclass if a class inherits from multiple classes.

arguments
className (1,1) string
propertyName (1,1) string
end

result = string.empty; % Initialize an empty cell array
currentType = className; % Start with the specific type

% Iterate over class and superclasses to detect property names for
% unnamed groups across the type hierarchy.
while ~strcmp(currentType, 'types.untyped.MetaClass')

% Use MetaClass information to get class information
metaClass = meta.class.fromName(currentType);

% Get value of GroupPropertyNames if this class is a subclass of
% the HasUnnamedGroups subclass.
isProp = strcmp({metaClass.PropertyList.Name}, propertyName);
if any(isProp)
result = [result, ...
string(metaClass.PropertyList(isProp).DefaultValue)]; %#ok<AGROW>
end

if isempty(metaClass.SuperclassList)
break % Reached the base type
end

% Get superclass for next iteration. NWB parent type should
% always be the first superclass in the list
currentType = metaClass.SuperclassList(1).Name;
end

result = unique(result, 'stable');
end
Loading