Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6e13345
feat: add aligned dynamic table categories
ehennestad Jun 21, 2026
385e2d7
test: move aligned table test double
ehennestad Jun 21, 2026
2430e44
fix: AlignedDynamicTable.checkConfig should not require materialzed c…
ehennestad Jun 21, 2026
af8366e
Update icephys tutorial to use the new api
ehennestad Jun 21, 2026
526e26f
fix: allow gradual aligned category construction
ehennestad Jun 21, 2026
1687858
Squashed commit of the following:
ehennestad Jun 22, 2026
7b8e760
refactor: extract aligned table height helpers
ehennestad Jun 22, 2026
dada843
refactor: rename aligned table consistency setup
ehennestad Jun 22, 2026
1c1a18c
refactor: clarify aligned table category names
ehennestad Jun 22, 2026
d23aa6a
Update AlignedDynamicTableBase.m
ehennestad Jun 23, 2026
1e27621
Update AlignedDynamicTableBase.m
ehennestad Jun 23, 2026
8797e93
Consolidate internal dynamic table helpers
ehennestad Jun 23, 2026
bcf7f5e
Consolidate function for checking if type is ancestor of another type
ehennestad Jun 23, 2026
be73305
refactor: share dynamic table height state
ehennestad Jun 23, 2026
42c8f72
Update alignedDynamicTableTest.m
ehennestad Jun 24, 2026
7e007ec
test: move aligned table coverage to system tests
ehennestad Jun 24, 2026
b79a539
Update getColumnRowHeight.m
ehennestad Jun 24, 2026
f9af70c
Fix isempty for Set; need to check if count == 0
ehennestad Jun 24, 2026
aafdd63
Create DynamicTable.m
ehennestad Jun 24, 2026
df29394
Update AlignedDynamicTableTest.m
ehennestad Jun 24, 2026
396c54d
Update AlignedDynamicTableBase.m
ehennestad Jun 24, 2026
4b3f900
refactor: simplify dynamic table id initialization
ehennestad Jun 24, 2026
26b6516
Update AlignedDynamicTableTest.m
ehennestad Jun 24, 2026
5940f7d
Flip argument order to improve readability
ehennestad Jun 24, 2026
60d8028
Use constant properties instead of methods to store schema-declared c…
ehennestad Jun 24, 2026
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
50 changes: 49 additions & 1 deletion +file/fillClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@
superclassNames{end+1} = 'matnwb.mixin.HasUnnamedGroups';
end

if strcmp(name, 'AlignedDynamicTable')
superclassNames{end+1} = 'matnwb.neurodata.AlignedDynamicTableBase';
end

%% return classfile string
classDefinitionHeader = [...
'classdef ' name ' < ' strjoin(superclassNames, ' & ') newline... %header, dependencies
Expand Down Expand Up @@ -178,8 +182,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 @@ -231,3 +241,41 @@
sprintf(' GroupPropertyNames = {%s}', strjoin(strcat('''', anonNames, ''''), ', ') ), ...
'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
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} = ' types.util.dynamictable.checkConfig(obj);';
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 @@
' types.util.dynamictable.checkConfig(obj)\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
Loading