You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

471 lines
18 KiB
Matlab

7 years ago
function [OPT Set Default] = setproperty(OPT, inputCell, varargin)
% SETPROPERTY generic routine to set values in PropertyName-PropertyValue pairs
%
% Routine to set properties based on PropertyName-PropertyValue
% pairs (aka <keyword,value> pairs). Can be used in any function
% where PropertyName-PropertyValue pairs are used.
%
%
% [OPT Set Default] = setproperty(OPT, inputCell, varargin)
%
% input:
% OPT = structure in which fieldnames are the keywords and the values
% are the defaults
% inputCell = must be a cell array containing single struct, or a set of
% 'PropertyName', PropertyValue,... pairs. It is best practice
% to pass the varargin of the calling function as the 2nd argument.
% Property names must be strings. If they contain a dot (.) this is
% interpreted a field separator. This can be used to assign
% properties on a subfield level.
% varargin = series of 'PropertyName', PropertyValue,... pairs to set
% methods for setproperty itself.
%
% output:
% OPT = structure, similar to the input argument OPT, with possibly
% changed values in the fields
% Set = structure, similar to OPT, values are true where OPT has been
% set (and possibly changed)
% Default = structure, similar to OPT, values are true where the values of
% OPT are equal to the original OPT
%
% Example calls:
%
% [OPT Set Default] = setproperty(OPT, vararginOfCallingFunction)
% [OPT Set Default] = setproperty(OPT, {'PropertyName', PropertyValue,...})
% [OPT Set Default] = setproperty(OPT, {OPT2})
%
% Any number of leading structs with
% any number of trailing <keyword,value> pairs:
% [OPT Set Default] = setproperty(OPT1, OPT2, ..., OPTn)
% [OPT Set Default] = setproperty(OPT1, OPT2, ..., OPTn,'PropertyName', PropertyValue,...)
%
% Different methods for dealing with class changes of variables, or
% extra fields (properties that are not in the input structure) can
% be defined as property-value pairs. Valid properties are
% onExtraField and onClassChange:
%
% PROPERTY VALUE
% onClassChange: ignore ignore (default)
% warn throw warning
% error throw error
% onExtraField: silentIgnore silently ignore the field
% warnIgnore ignore the field and throw warning
% silentAppend silently append the field to OPT
% warnAppend append the field to OPT and throw warning
% error throw error (default)
%
% Example calls:
%
% [OPT Set Default] = setproperty(OPT, vararginOfCallingFunction,'onClassChange','warn')
% [OPT Set Default] = setproperty(OPT, {'PropertyName', PropertyValue},'onExtraField','silentIgnore')
%
%
% Example:
%
% +------------------------------------------->
% function y = dosomething(x,'debug',1)
% OPT.debug = 0;
% OPT = setproperty(OPT, varargin);
% y = x.^2;
% if OPT.debug; plot(x,y);pause; end
% +------------------------------------------->
%
% legacy syntax is also supported, but using legacy syntax prohibits the
% setting of onClassChange and onExtraField methods:
%
% [OPT Set Default] = setproperty(OPT, varargin{:})
% OPT = setproperty(OPT, 'PropertyName', PropertyValue,...)
% OPT = setproperty(OPT, OPT2)
%
% input:
% OPT = structure in which fieldnames are the keywords and the values are the defaults
% varargin = series of PropertyName-PropertyValue pairs to set
% OPT2 = is a structure with the same fields as OPT.
%
% Internally setproperty translates OPT2 into a set of
% PropertyName-PropertyValue pairs (see example below) as in:
% OPT2 = struct( 'propertyName1', 1,...
% 'propertyName2', 2);
% varcell = reshape([fieldnames(OPT2)'; struct2cell(OPT2)'], 1, 2*length(fieldnames(OPT2)));
% OPT = setproperty(OPT, varcell{:});
%
% Change log:
% 2011-09-30: full code rewrite to include:
% - setpropertyInDeeperStruct functionality
% - user defined handling of extra fields
% - class change warning/error message
%
% See also: VARARGIN, STRUCT, MERGESTRUCTS
%% Copyright notice
% --------------------------------------------------------------------
% Copyright (C) 2009 Delft University of Technology
% C.(Kees) den Heijer
%
% C.denHeijer@TUDelft.nl
%
% Faculty of Civil Engineering and Geosciences
% P.O. Box 5048
% 2600 GA Delft
% The Netherlands
%
% This library is free software; you can redistribute it and/or
% modify it under the terms of the GNU Lesser General Public
% License as published by the Free Software Foundation; either
% version 2.1 of the License, or (at your option) any later version.
%
% This library is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
% Lesser General Public License for more details.
%
% You should have received a copy of the GNU Lesser General Public
% License along with this library; if not, write to the Free Software
% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
% USA
% or http://www.gnu.org/licenses/licenses.html, http://www.gnu.org/, http://www.fsf.org/
% --------------------------------------------------------------------
% This tools is part of <a href="http://OpenEarth.Deltares.nl">OpenEarthTools</a>.
% OpenEarthTools is an online collaboration to share and manage data and
% programming tools in an open source, version controlled environment.
% Sign up to recieve regular updates of this function, and to contribute
% your own tools.
%% Version <http://svnbook.red-bean.com/en/1.5/svn.advanced.props.special.keywords.html>
% Created: 26 Feb 2009
% Created with Matlab version: 7.4.0.287 (R2007a)
% $Id: setproperty.m 8036 2013-02-05 21:06:43Z boer_g $
% $Date: 2013-02-05 22:06:43 +0100 (Tue, 05 Feb 2013) $
% $Author: boer_g $
% $Revision: 8036 $
% $HeadURL: https://svn.oss.deltares.nl/repos/openearthtools/trunk/matlab/setproperty.m $
% $Keywords: $
%% shortcut function if there is nothing to set (1)
if nargin==1||isempty(inputCell)
if nargout > 1 % process Set
flds1 = fieldnames(OPT);
Set = [flds1,repmat({false},size(flds1))]';
Set = struct(Set{:});
end
if nargout > 2 % process Default
Default = [flds1,repmat({true},size(flds1))]';
Default = struct(Default{:});
end
return
end
%% check and parse inputCell (usually the varargin struct in the calling function)
% determine mode from class of the second argument
switch(class(inputCell))
case 'struct'
% recursively let setproperty peel all leading structs
% with optional trailing keyword,value> pairs
inputCell = setproperty(inputCell,varargin{:});
inputCell = struct2arg(inputCell);
varargin = {};
case 'char'
% legay syntax mode
inputCell = [{inputCell} varargin];
varargin = {};
case 'cell'
% recursively let setproperty peel all leading structs from cell
if isstruct(inputCell{1})
inputCell = {setproperty(inputCell{:})};
end
otherwise
error('SETPROPERTY:inputCell',...
'Second input must be a cell, (or a char or struct for legacy syntax)')
end
%% shortcut function if there is nothing to set (2)
if numel(inputCell) == 1 && isempty(inputCell{1})
if nargout > 1 % process Set
flds1 = fieldnames(OPT);
Set = [flds1,repmat({false},size(flds1))]';
Set = struct(Set{:});
end
if nargout > 2 % process Default
Default = [flds1,repmat({true},size(flds1))]';
Default = struct(Default{:});
end
return
end
if odd(length(inputCell))
%then length must be 1
if length(inputCell) == 1
%then the inputCell must be a structure
if ~isstruct(inputCell{1})
error('SETPROPERTY:inputCell',...
'Second argument inputCell must be a cell containing a single struct or property/value pairs')
end
flds2 = fieldnames(inputCell{1})';
newVal = struct2cell(inputCell{1})';
else
error('SETPROPERTY:inputCell',...
'Second argument inputCell must be a cell containing a single struct or property/value pairs')
end
else
flds2 = inputCell(1:2:end);
if ~all(cellfun(@ischar,flds2))
error('SETPROPERTY:inputCell',...
'Second argument inputCell, property names should be strings')
end
newVal = inputCell(2:2:end);
end
%% check and parse varargin
if length(varargin) > 4
error('Too many input arguments to setpropoerty.');
end
% error(nargchk(0, 4, length(varargin), 'struct'))
if odd(length(varargin))
error('SETPROPERTY:varargin',...
'Set onClassChange and onExtraField with the varargin, as keyword value pairs');
end
onExtraField = 'error';
onClassChange = 'ignore';
if ~isempty(varargin)
for ii = 1:2:length(varargin)
switch varargin{ii}
case 'onClassChange'
if ischar(varargin{ii+1}) && ismember(varargin(ii+1),...
{'ignore','warn','error'})
onClassChange = varargin{ii+1};
else
error('SETPROPERTY:InvalidMethod',...
'Valid methods for onClassChange are: ignore, warn and error');
end
case 'onExtraField'
if ischar(varargin{ii+1}) && ismember(varargin(ii+1),...
{'silentAppend','warnAppend','silentIgnore','warnIgnore','error'})
onExtraField = varargin{ii+1};
else
error('SETPROPERTY:InvalidMethod',...
'Valid methods for onExtraField are: append, silentIgnore, warnIgnore, error');
end
otherwise
error('SETPROPERTY:InvalidKeyword', ...
'Supported keywords are onClassChange and onExtraField');
end
end
end
switch onClassChange
case 'warn'
warnOnClassChange = true;
errorOnClassChange = false;
case 'error'
warnOnClassChange = false;
errorOnClassChange = true;
case 'ignore'
warnOnClassChange = false;
errorOnClassChange = false;
end
%% copy the original OPT only if Default has to be returned
if nargout > 2
OPToriginal = OPT;
end
%% check field names, and distinguish between field to set and extra fields
% flds1 contains field names of OPT
flds1 = fieldnames(OPT);
% fldsCell contains field names to assign, split per subfield
fldsCell = regexp(flds2,'[^\.]*','match');
fldsCellLen = cellfun(@length,fldsCell);
% check if all field names are valid to assign
allFieldNames = [fldsCell{:}];
validFieldName = cellfun(@(x) (isvarname(x) | iskeyword(x)),allFieldNames); % a field like 'case' is allowed also, but is not a valid varname
if ~all(validFieldName)
error('SETPROPERTY:invalidFieldName',...
'\nThe following field name is not valid: %s',allFieldNames{~validFieldName});
end
% identify fldsToSet for simple field names (without subfields)
% fldsToSet = ismember(flds2,flds1);
% As ismember is rather slow, this is replaced by more optimized code
fldsToSet = false(size(flds2));
for ii = find(fldsCellLen==1)
fldsToSet(ii) = any(strcmp(flds2(ii),flds1));
end
% identify fldsToSet for field names with subfields)
for ii = find(fldsCellLen>1)
fldsToSet(ii) = isfield2(OPT,fldsCell{ii});
end
% all other fields are either to be set, or extra
fldsExtra = ~fldsToSet;
%% process fldsToSet
if any(fldsToSet)
for ii = find(fldsToSet)
switch length(fldsCell{ii})
case 1
if warnOnClassChange || errorOnClassChange
class1 = class(OPT.(fldsCell{ii}{1}));
class2 = class(newVal{ii});
classChange(flds2{ii},class1,class2,warnOnClassChange,errorOnClassChange);
end
OPT.(fldsCell{ii}{1}) = newVal{ii};
case 2
if warnOnClassChange || errorOnClassChange
class1 = class(OPT.(fldsCell{ii}{1}).(fldsCell{ii}{2}));
class2 = class(newVal{ii});
classChange(flds2{ii},class1,class2,warnOnClassChange,errorOnClassChange);
end
OPT.(fldsCell{ii}{1}).(fldsCell{ii}{2}) = newVal{ii};
otherwise
if warnOnClassChange || errorOnClassChange
class1 = class(getfield(OPT,fldsCell{ii}{:}));
class2 = class(newVal{ii});
classChange(flds2{ii},class1,class2,warnOnClassChange,errorOnClassChange);
end
OPT = setfield(OPT,fldsCell{ii}{:},newVal{ii});
end
end
end
%% deal with fldsExtra
if any(fldsExtra)
switch lower(onExtraField)
case {'silentappend','warnappend'}
% append extra fields to OPT
for ii = find(fldsExtra)
switch length(fldsCell{ii})
case 1
OPT.(flds2{ii}) = newVal{ii};
otherwise
OPT = setfield(OPT,fldsCell{ii}{:},newVal{ii});
end
end
if strcmpi(onExtraField,'warnAppend')
% throw a warning
warning('SETPROPERTY:ExtraField',...
'\nThe following field is Extra and appended to OPT: %s',flds2{fldsExtra});
end
case 'silentignore'
% do nothing, silently ignore extra fields
case 'warnignore'
warning('SETPROPERTY:ExtraField',...
'\nThe following field is Extra and thus ignored: %s',flds2{fldsExtra});
case 'error'
error('SETPROPERTY:ExtraField',...
'\nThe following field is Extra: %s',flds2{fldsExtra});
end
end
%% assign Output
if nargout > 1 % process Set
Set = [flds1,repmat({false},size(flds1))]';
Set = struct(Set{:});
if any(fldsToSet)
for ii = find(fldsToSet)
switch length(fldsCell{ii})
case 1
Set.(fldsCell{ii}{1}) = true;
otherwise % this routine is rather slow, would be nice to add case 2 to cover the most common calls
for nn = 1:length(fldsCell{ii})-1
if ~isstruct(getfield(Set,fldsCell{ii}{1:nn})) %#ok<*GFLD>
substruct = [fieldnames(getfield(OPT,fldsCell{ii}{1:nn})),...
repmat({false},size(fieldnames(getfield(OPT,fldsCell{ii}{1:nn}))))]';
Set = setfield(Set,fldsCell{ii}{1:nn},[]); %#ok<*SFLD>
Set = setfield(Set,fldsCell{ii}{1:nn},struct(substruct{:}));
end
end
Set = setfield(Set,fldsCell{ii}{:},true);
end
end
end
if any(fldsExtra)
switch lower(onExtraField)
case {'silentappend','warnappend'}
for ii = find(fldsExtra)
switch length(fldsCell{ii})
case 1
Set.(flds2{ii}) = true;
otherwise
Set = setfield(Set,fldsCell{ii}{:},true);
end
end
end
end
end
if nargout > 2 % process Default
Default = [flds1,repmat({true},size(flds1))]';
Default = struct(Default{:});
if any(fldsToSet)
for ii = find(fldsToSet)
switch length(fldsCell{ii})
case 1
Default.(fldsCell{ii}{1}) = ...
isequalwithequalnans(OPToriginal.(fldsCell{ii}{1}),OPT.(fldsCell{ii}{1}));
otherwise % this routine is rather slow, would be nice to add case 2 to cover the most common calls
for nn = 1:length(fldsCell{ii})-1
if ~isstruct(getfield(Default,fldsCell{ii}{1:nn})) %#ok<*GFLD>
substruct = [fieldnames(getfield(OPT,fldsCell{ii}{1:nn})),...
repmat({true},size(fieldnames(getfield(OPT,fldsCell{ii}{1:nn}))))]';
Default = setfield(Default,fldsCell{ii}{1:nn},[]); %#ok<*SFLD>
Default = setfield(Default,fldsCell{ii}{1:nn},struct(substruct{:}));
end
end
Default = setfield(Default,fldsCell{ii}{:},...
isequalwithequalnans(getfield(OPToriginal,fldsCell{ii}{:}),getfield(OPT,fldsCell{ii}{:})));
end
end
end
if any(fldsExtra)
switch lower(onExtraField)
case {'silentappend','warnappend'}
for ii = find(fldsExtra)
switch length(fldsCell{ii})
case 1
Default.(flds2{ii}) = false;
otherwise
Default = setfield(Default,fldsCell{ii}{:},false);
end
end
end
end
end
function tf = isfield2(OPT,fldsCell)
if isfield(OPT,fldsCell{1})
if length(fldsCell) > 1
tf = isfield2(OPT.(fldsCell{1}),fldsCell(2:end));
else
tf = true;
end
else
tf = false;
end
function classChange(fld,class1,class2,warnOnClassChange,errorOnClassChange)
if ~strcmp(class1,class2)
if warnOnClassChange
warning('SETPROPERTY:ClassChange', ...
'Class change of field ''%s'' from %s to %s',...
fld,class1,class2);
elseif errorOnClassChange
error('SETPROPERTY:ClassChange', ...
'Class change of field ''%s'' from %s to %s not allowed',...
fld,class1,class2);
end
end
function out = odd(in)
%ODD test whether number if odd
out = mod(in,2)==1;
%% EOF