diff --git a/+file/fillValidators.m b/+file/fillValidators.m index ebd9cbf3..fdb7b5f0 100644 --- a/+file/fillValidators.m +++ b/+file/fillValidators.m @@ -14,6 +14,8 @@ else continue end + elseif isa(prop, 'file.Link') + validationBody = fillLinkValidation(nm, prop, namespacereg); else if startsWith(class(prop), 'file.') validationBody = fillUnitValidation(nm, prop, namespacereg); @@ -173,6 +175,31 @@ end end +function validationStr = fillLinkValidation(name, prop, namespacereg) + fullName = namespacereg.getFullClassName(prop.type); + + % Create a validation function body that 1) checks (validates) the + % target if the input is a SoftLink type, otherwise 2) checks (validates) + % if the expected (target) type is provided. If the validation passes + % and the value is not empty, it is wrapped in a SoftLink. + + validationStr = sprintf([ ... + 'if isa(val, ''types.untyped.SoftLink'')\n', ... + ' if isprop(val, ''target'')\n', ... + ' types.util.checkDtype(''%s'', ''%s'', val.target);\n', ... + ' end\n', ... + 'else\n', ... + ' %s\n', ... + ' if ~isempty(val)\n', ... + ' val = types.untyped.SoftLink(val);\n', ... + ' end\n', ... + 'end' ... + ], ... + name, fullName, ... + fillDtypeValidation(name, fullName) ... + ); +end + function fdvstr = fillDimensionValidation(type, shape) if strcmp(type, 'any') fdvstr = ''; diff --git a/+tests/+unit/linkTest.m b/+tests/+unit/linkTest.m index 4f5e688f..05f462f3 100644 --- a/+tests/+unit/linkTest.m +++ b/+tests/+unit/linkTest.m @@ -88,3 +88,25 @@ function testExternalResolution(testCase) % for links, deref() should return its own link. tests.util.verifyContainerEqual(testCase, metaExternalLink.deref().deref(), expected); end + +function testDirectTypeAssignmentToSoftLinkProperty(testCase) + device = types.core.Device('description', 'test_device'); + electrodeGroup = types.core.ElectrodeGroup(... + 'description', 'test_group', ... + 'device', device); + + testCase.verifyClass(electrodeGroup.device, 'types.untyped.SoftLink') + testCase.verifyClass(electrodeGroup.device.target, 'types.core.Device') +end + +function testWrongTypeInSoftLinkAssignment(testCase) + + function createElectrodeGroupWithWrongDeviceType() + not_a_device = types.core.OpticalChannel('description', 'test_channel'); + electrodeGroup = types.core.ElectrodeGroup(... + 'description', 'test_group', ... + 'device', not_a_device); %#ok + end + testCase.verifyError(@createElectrodeGroupWithWrongDeviceType, ... + 'NWB:TypeCorrection:InvalidConversion') +end diff --git a/+types/+core/ClusterWaveforms.m b/+types/+core/ClusterWaveforms.m index be7285f1..a234c0f3 100644 --- a/+types/+core/ClusterWaveforms.m +++ b/+types/+core/ClusterWaveforms.m @@ -53,7 +53,16 @@ %% VALIDATORS function val = validate_clustering_interface(obj, val) - val = types.util.checkDtype('clustering_interface', 'types.core.Clustering', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('clustering_interface', 'types.core.Clustering', val.target); + end + else + val = types.util.checkDtype('clustering_interface', 'types.core.Clustering', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end function val = validate_waveform_filtering(obj, val) val = types.util.checkDtype('waveform_filtering', 'char', val); diff --git a/+types/+core/CorrectedImageStack.m b/+types/+core/CorrectedImageStack.m index 072b7f57..58195421 100644 --- a/+types/+core/CorrectedImageStack.m +++ b/+types/+core/CorrectedImageStack.m @@ -50,7 +50,16 @@ val = types.util.checkDtype('corrected', 'types.core.ImageSeries', val); end function val = validate_original(obj, val) - val = types.util.checkDtype('original', 'types.core.ImageSeries', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('original', 'types.core.ImageSeries', val.target); + end + else + val = types.util.checkDtype('original', 'types.core.ImageSeries', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end function val = validate_xy_translation(obj, val) val = types.util.checkDtype('xy_translation', 'types.core.TimeSeries', val); diff --git a/+types/+core/DecompositionSeries.m b/+types/+core/DecompositionSeries.m index 118c7e5c..7e792986 100644 --- a/+types/+core/DecompositionSeries.m +++ b/+types/+core/DecompositionSeries.m @@ -118,7 +118,16 @@ val = types.util.checkDtype('source_channels', 'types.hdmf_common.DynamicTableRegion', val); end function val = validate_source_timeseries(obj, val) - val = types.util.checkDtype('source_timeseries', 'types.core.TimeSeries', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('source_timeseries', 'types.core.TimeSeries', val.target); + end + else + val = types.util.checkDtype('source_timeseries', 'types.core.TimeSeries', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end %% EXPORT function refs = export(obj, fid, fullpath, refs) diff --git a/+types/+core/ElectrodeGroup.m b/+types/+core/ElectrodeGroup.m index d44737b8..96c8af02 100644 --- a/+types/+core/ElectrodeGroup.m +++ b/+types/+core/ElectrodeGroup.m @@ -68,7 +68,16 @@ types.util.checkDims(valsz, validshapes); end function val = validate_device(obj, val) - val = types.util.checkDtype('device', 'types.core.Device', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('device', 'types.core.Device', val.target); + end + else + val = types.util.checkDtype('device', 'types.core.Device', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end function val = validate_location(obj, val) val = types.util.checkDtype('location', 'char', val); diff --git a/+types/+core/EventDetection.m b/+types/+core/EventDetection.m index 6a7cfe46..19c8f062 100644 --- a/+types/+core/EventDetection.m +++ b/+types/+core/EventDetection.m @@ -78,7 +78,16 @@ types.util.checkDims(valsz, validshapes); end function val = validate_source_electricalseries(obj, val) - val = types.util.checkDtype('source_electricalseries', 'types.core.ElectricalSeries', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('source_electricalseries', 'types.core.ElectricalSeries', val.target); + end + else + val = types.util.checkDtype('source_electricalseries', 'types.core.ElectricalSeries', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end function val = validate_source_idx(obj, val) val = types.util.checkDtype('source_idx', 'int32', val); diff --git a/+types/+core/ImageMaskSeries.m b/+types/+core/ImageMaskSeries.m index ae2c1710..4db9efbc 100644 --- a/+types/+core/ImageMaskSeries.m +++ b/+types/+core/ImageMaskSeries.m @@ -32,7 +32,16 @@ %% VALIDATORS function val = validate_masked_imageseries(obj, val) - val = types.util.checkDtype('masked_imageseries', 'types.core.ImageSeries', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('masked_imageseries', 'types.core.ImageSeries', val.target); + end + else + val = types.util.checkDtype('masked_imageseries', 'types.core.ImageSeries', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end %% EXPORT function refs = export(obj, fid, fullpath, refs) diff --git a/+types/+core/ImageSeries.m b/+types/+core/ImageSeries.m index 1fbdd219..2d01e1c3 100644 --- a/+types/+core/ImageSeries.m +++ b/+types/+core/ImageSeries.m @@ -76,7 +76,16 @@ types.util.checkDims(valsz, validshapes); end function val = validate_device(obj, val) - val = types.util.checkDtype('device', 'types.core.Device', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('device', 'types.core.Device', val.target); + end + else + val = types.util.checkDtype('device', 'types.core.Device', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end function val = validate_dimension(obj, val) val = types.util.checkDtype('dimension', 'int32', val); diff --git a/+types/+core/ImagingPlane.m b/+types/+core/ImagingPlane.m index c47afe39..e60f420d 100644 --- a/+types/+core/ImagingPlane.m +++ b/+types/+core/ImagingPlane.m @@ -137,7 +137,16 @@ types.util.checkDims(valsz, validshapes); end function val = validate_device(obj, val) - val = types.util.checkDtype('device', 'types.core.Device', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('device', 'types.core.Device', val.target); + end + else + val = types.util.checkDtype('device', 'types.core.Device', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end function val = validate_excitation_lambda(obj, val) val = types.util.checkDtype('excitation_lambda', 'single', val); diff --git a/+types/+core/IndexSeries.m b/+types/+core/IndexSeries.m index 8ba26b10..30e53cf6 100644 --- a/+types/+core/IndexSeries.m +++ b/+types/+core/IndexSeries.m @@ -128,10 +128,28 @@ end end function val = validate_indexed_images(obj, val) - val = types.util.checkDtype('indexed_images', 'types.core.Images', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('indexed_images', 'types.core.Images', val.target); + end + else + val = types.util.checkDtype('indexed_images', 'types.core.Images', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end function val = validate_indexed_timeseries(obj, val) - val = types.util.checkDtype('indexed_timeseries', 'types.core.ImageSeries', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('indexed_timeseries', 'types.core.ImageSeries', val.target); + end + else + val = types.util.checkDtype('indexed_timeseries', 'types.core.ImageSeries', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end %% EXPORT function refs = export(obj, fid, fullpath, refs) diff --git a/+types/+core/IntracellularElectrode.m b/+types/+core/IntracellularElectrode.m index 6b1231e0..a0426703 100644 --- a/+types/+core/IntracellularElectrode.m +++ b/+types/+core/IntracellularElectrode.m @@ -119,7 +119,16 @@ types.util.checkDims(valsz, validshapes); end function val = validate_device(obj, val) - val = types.util.checkDtype('device', 'types.core.Device', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('device', 'types.core.Device', val.target); + end + else + val = types.util.checkDtype('device', 'types.core.Device', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end function val = validate_filtering(obj, val) val = types.util.checkDtype('filtering', 'char', val); diff --git a/+types/+core/OnePhotonSeries.m b/+types/+core/OnePhotonSeries.m index ed410612..3d5b28b2 100644 --- a/+types/+core/OnePhotonSeries.m +++ b/+types/+core/OnePhotonSeries.m @@ -104,7 +104,16 @@ types.util.checkDims(valsz, validshapes); end function val = validate_imaging_plane(obj, val) - val = types.util.checkDtype('imaging_plane', 'types.core.ImagingPlane', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('imaging_plane', 'types.core.ImagingPlane', val.target); + end + else + val = types.util.checkDtype('imaging_plane', 'types.core.ImagingPlane', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end function val = validate_intensity(obj, val) val = types.util.checkDtype('intensity', 'single', val); diff --git a/+types/+core/OptogeneticSeries.m b/+types/+core/OptogeneticSeries.m index 92176f50..b0a59fab 100644 --- a/+types/+core/OptogeneticSeries.m +++ b/+types/+core/OptogeneticSeries.m @@ -62,7 +62,16 @@ end end function val = validate_site(obj, val) - val = types.util.checkDtype('site', 'types.core.OptogeneticStimulusSite', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('site', 'types.core.OptogeneticStimulusSite', val.target); + end + else + val = types.util.checkDtype('site', 'types.core.OptogeneticStimulusSite', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end %% EXPORT function refs = export(obj, fid, fullpath, refs) diff --git a/+types/+core/OptogeneticStimulusSite.m b/+types/+core/OptogeneticStimulusSite.m index f463c067..7a2f6c88 100644 --- a/+types/+core/OptogeneticStimulusSite.m +++ b/+types/+core/OptogeneticStimulusSite.m @@ -71,7 +71,16 @@ types.util.checkDims(valsz, validshapes); end function val = validate_device(obj, val) - val = types.util.checkDtype('device', 'types.core.Device', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('device', 'types.core.Device', val.target); + end + else + val = types.util.checkDtype('device', 'types.core.Device', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end function val = validate_excitation_lambda(obj, val) val = types.util.checkDtype('excitation_lambda', 'single', val); diff --git a/+types/+core/PatchClampSeries.m b/+types/+core/PatchClampSeries.m index 28cc4446..cbe61d51 100644 --- a/+types/+core/PatchClampSeries.m +++ b/+types/+core/PatchClampSeries.m @@ -90,7 +90,16 @@ types.util.checkDims(valsz, validshapes); end function val = validate_electrode(obj, val) - val = types.util.checkDtype('electrode', 'types.core.IntracellularElectrode', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('electrode', 'types.core.IntracellularElectrode', val.target); + end + else + val = types.util.checkDtype('electrode', 'types.core.IntracellularElectrode', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end function val = validate_gain(obj, val) val = types.util.checkDtype('gain', 'single', val); diff --git a/+types/+core/PlaneSegmentation.m b/+types/+core/PlaneSegmentation.m index 5fd95183..492ae7ea 100644 --- a/+types/+core/PlaneSegmentation.m +++ b/+types/+core/PlaneSegmentation.m @@ -74,7 +74,16 @@ val = types.util.checkDtype('image_mask', 'types.hdmf_common.VectorData', val); end function val = validate_imaging_plane(obj, val) - val = types.util.checkDtype('imaging_plane', 'types.core.ImagingPlane', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('imaging_plane', 'types.core.ImagingPlane', val.target); + end + else + val = types.util.checkDtype('imaging_plane', 'types.core.ImagingPlane', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end function val = validate_pixel_mask(obj, val) val = types.util.checkDtype('pixel_mask', 'types.hdmf_common.VectorData', val); diff --git a/+types/+core/TwoPhotonSeries.m b/+types/+core/TwoPhotonSeries.m index e49afe47..b9eda919 100644 --- a/+types/+core/TwoPhotonSeries.m +++ b/+types/+core/TwoPhotonSeries.m @@ -68,7 +68,16 @@ types.util.checkDims(valsz, validshapes); end function val = validate_imaging_plane(obj, val) - val = types.util.checkDtype('imaging_plane', 'types.core.ImagingPlane', val); + if isa(val, 'types.untyped.SoftLink') + if isprop(val, 'target') + types.util.checkDtype('imaging_plane', 'types.core.ImagingPlane', val.target); + end + else + val = types.util.checkDtype('imaging_plane', 'types.core.ImagingPlane', val); + if ~isempty(val) + val = types.untyped.SoftLink(val); + end + end end function val = validate_pmt_gain(obj, val) val = types.util.checkDtype('pmt_gain', 'single', val);