www.gusucode.com > external 工具箱matlab源码程序 > external/interfaces/webservices/http/+matlab/+net/+http/HeaderField.m

    classdef (AllowedSubclasses={...
         ?matlab.net.http.field.AuthenticateField, ...
         ?matlab.net.http.field.AuthenticationInfoField, ...
         ?matlab.net.http.field.AuthorizationField, ...
         ?matlab.net.http.field.CaseInsensitiveStringField, ...
         ?matlab.net.http.field.CookieField, ...
         ?matlab.net.http.field.GenericField, ...
         ?matlab.net.http.field.IntegerField, ...
         ?matlab.net.http.field.LocationField, ...
         ?matlab.net.http.field.MediaRangeField, ...
         ?matlab.net.http.field.ScalarDateField, ...
         ?matlab.net.http.field.SetCookieField, ...
         ?matlab.net.http.field.URIReferenceField, ...
        }) HeaderField < matlab.mixin.Heterogeneous 
% HeaderField Field of an HTTP header
%   This class implements a header field of an HTTP message, providing conversions
%   between strings in the header and MATLAB arrays and structs.  This field has two
%   properties, Name and Value, both strings, and in the simplest case these
%   properties may be set to arbitrary values, subject to constraints on the
%   characters allowed in HTTP header fields.
%
%   The Name field defines the "type" of this field, and for some commonly used
%   fields there are subclasses in the matlab.net.http.field package that support
%   those field types.  For example matlab.net.http.field.DateField implements 'Date'
%   header field, so if you are creating one of those it is better to use that
%   subclass, which accepts a value in the form of a date-time string or a datetime
%   object.  If, instead, you use HeaderField to create a 'Date' field, any Value you
%   specify for the field will be interpreted and enforced by the DateField class,
%   even though this object is not an instance of that class.  Likewise, if you use
%   the convert method to convert the field value to a MATLAB datetime, the
%   DateField's convert method will be used.
%
%   To see a list of all supported subclasses, use HeaderField.displaySubclasses.
%
%   HeaderField properties:
%      Name      - name of the field (string)
%      Value     - value of the field (string)
%
%   HeaderField methods:
%      HeaderField       - constructor
%      convert           - return Value as MATLAB type
%      parse             - parse Value generically and return strings
%      eq, ==            - compare for functional equivalence
%      isequal           - compare for functional equivalence
%      char, string      - return HeaderField as a character vector or string
%      displaySubclasses - (static) display list of supported subclasses
%
% See also HeaderField, displaySubclasses, matlab.net.http.field.DateField

%   The following documentation is for internal use only.  It's behavior is likely to
%   change in a future release.
%
%   Subclass authors: All subclasses must be defined in the matlab.net.http.field
%   package.  Every subclass must implement getSupportedNames() to return the field
%   name, or cell array of names, that it implements.
%
%   All subclasses should override convert() and one or more of the protected methods
%   in this class to customize the subclass behavior.  For example
%   getStringException() and scalarToString() should be overridden to check or
%   convert supplied field values that the caller supplies, to strings that are
%   stored in the header, and parseField() and/or parseElement() should be
%   implemented to convert a string in the header to MATLAB data types.
%
%   If a subclass's getSupportedNames() function returns vector of strings, the
%   subclass must provide a 2-argument constructor taking NAME and VALUE parameters.
%   If it returns a scalar string it must provide a 1-argument constructor that takes
%   just a VALUE. 
%   
%   A subclass constructor should invoke its superclass constructor
%   with the NAME and VALUE parameters.  The subclass need not validate these
%   parameters in its constructor: the infrastructure will insure that the NAME is
%   one of those allowed by getSupportedNames(), and will invoke the subclass's
%   scalarToString() and getStringException() to insure that the VALUE is valid.

%   Copyright 2015-2016 The MathWorks, Inc.    

    properties (AbortSet)
        % Name - name of the field (a string)
        %    When you set this property you effectively change the field's type, that
        %    may determine what values of the Value property are legal.
        %    
        %    If this is an instance of a subclass implementing a specific header
        %    field type, it will likely enforce constraints on the name you can set.
        %    If you set this to [] or an empty string, the value will become [].
        Name
        % Value - value of the field (a string)
        %    When you read this property you always get a string representing the
        %    value in the field.  When you set this property the value may be any
        %    type acceptable to this field based on the Name property and/or the
        %    class of this object, and the result will be converted to a string.  For
        %    field types that have a default value, specifying an empty string such
        %    as '' or string('') will insert that default.  Specifying an empty
        %    double, [], causes this field not to be added by RequestMessage.send or
        %    RequestMessage.complete when completing the message.  Fields with []
        %    values are not included in the completed message.
        %
        %    See also Name, parse, convert, RequestMessage
        Value
        %    For internal use only: Subclass authors may control how values are
        %    validated and converted by overriding various protected methods in this
        %    class.  Default behavior, if this is not a subclass, is described in the
        %    valueToString method.
    end
    
    properties (Constant, Hidden)
        % TokenChars - characters allowed in a token
        %    This is a regular expression that matches all characters allowed in a
        %    token of a header, as defined in RFC 7320, <a href="http://tools.ietf.org/html/rfc7230#section-3.2.6">section 3.2.6</a>.
        %    You can use this to assist in your own parsing of header fields.
        TokenChars = string('-!#$%&''*+.^`|~a-zA-Z_0-9')
    end
    
    methods 
        function obj = set.Value(obj, value)
        % If value is an empty string, either '' or "", gets the default value from
        % getDefaultValue().  Invokes validateValue to check and/or convert the
        % value.
            if (isempty(value) && ischar(value)) || ...
                    (isstring(value) && isscalar(value) && value=='')
                value = obj.getDefaultValue();
            end
            obj.Value = validateValue(obj, obj.Name, value);  %#ok<MCSUP>
        end
        
        function obj = set.Name(obj, name)
        % Verifies that name is in getSupportedNames of this object, and invokes
        % validateValue to check that the value, if already set, is permitted for
        % this name.
            import matlab.net.internal.getString
            supportedNames = obj.getSupportedNames();
            name = strtrim(getString(name, class(obj), 'Name', true)); 
            if name==''
                name = [];
            elseif ~isempty(name)
                if ~isempty(supportedNames) 
                    if ~any(strcmpi(name,supportedNames))
                        if ischar(supportedNames)
                            supportedNames = {supportedNames};
                        end
                        error(message('MATLAB:http:BadName', char(name), class(obj), ...
                                      char(strjoin(supportedNames)))); % TBD string
                    end
                elseif ~obj.isBaseClass() && ...
                       ~isa(obj, 'matlab.net.http.field.GenericField')  
                    % Class of obj allows any name and it's not this base class or
                    % instance of GenericField.  Make sure user doesn't choose a name
                    % implemented by some other class, as that could result in
                    % unintentional bypass of value validation.
                    otherclass = matlab.net.http.internal.PackageMonitor.getClassForName(name);
                    match = @(x) x{1}{1};
                    nameOf = @(c) match(regexp(c, '\.([^.]+)$','tokens'));
                    if ~isempty(otherclass) && ~(metaclass(obj) >= otherclass)
                        error(message('MATLAB:http:NameUsedByOtherClass', ...
                            nameOf(class(obj)), char(name), nameOf(otherclass.Name)));
                    end
                end
                % get any chars illegal in a name
                bad = regexp(name, '[^' + obj.TokenChars + ']', 'match', 'once');
                if ~isempty(bad) && ~ismissing(bad) && bad ~= ''
                    error(message('MATLAB:http:BadCharInProp', ...
                                  char(bad), char(name), 'HeaderField.Name'));
                end
                validateValue(obj, name, obj.Value);   %#ok<MCSUP>
            end
            obj.Name = name;
        end
        
        function obj = HeaderField(varargin)
        % HeaderField - Constructor for header field array
        %   FIELDS = HeaderField(NAME1,VALUE1,...,NAMEn,VALUEn) creates HeaderFields
        %   with the specified names and values,
        %
        %     NAME - name of the field (a string)
        %     VALUE - value of the field (a string or type appropriate for the NAME)
        %
        %   Either argument may be [].  If the last VALUE is missing it is treated 
        %   as empty.
        %
        %   This constructor creates fields whose class is HeaderField.  If you are
        %   creating a field of a class defined in the matlab.net.http.field package,
        %   you should use that class constructor instead.  For a list of these
        %   classes, use displaySubclasses.
        %
        %   For example these two both create a 'Content-Type' field with the same
        %   Name and Value:
        %
        %     ctf = matlab.net.http.HeaderField('Content-Type','text/plain');
        %     ctf = matlab.net.http.field.ContentTypeField('text/plain');
        %  
        %   The second form is preferred because you cannot misspell the field name
        %   (as it is derived from the class name), while the first will accept any
        %   field name and you may not find out about the error until the server
        %   rejects the message.  In fact, most servers silently ignore unknown field
        %   names.
        %
        %   However, assuming the field name in the first case is correct, the same
        %   validation of the VALUE takes place (in this case, that it is a valid
        %   media-type) regardless of which constructor you use.
        %
        %   If you want to create a particular HeaderField with a Name and Value that
        %   this constructor rejects, use GenericField.
        %
        % See also Name, Value, displaySubclasses,
        % matlab.net.http.field.ContentTypeField, matlab.net.http.field.GenericField
            if nargin ~= 0
                if nargin == 1 && isempty(varargin{1})
                    % This to support coercion of [] to empty array
                    obj = matlab.net.http.HeaderField.empty;
                else
                    if mod(nargin,2) > 0
                        varargin{nargin+1} = [];
                    end
                    max = length(varargin)/2;
                    obj = repmat(obj,1,max);
                    for i = max : -1 : 1
                        i2 = i*2;
                        obj(i).Name = varargin{i2-1};
                        obj(i).Value = varargin{i2};
                    end
                end
            end
        end
        
        function value = convert(obj, varargin) 
        % convert Return the value of a header field converted to a MATLAB type
        %   VALUES = convert(FIELDS) returns an array or cell array that is the result
        %   of calling convert on each header field in FIELDs.  If there is a custom
        %   class for the field in the matlab.net.http.field package based on the
        %   field's Name, invokes it to convert the Value to a MATLAB value or object.
        %   Throws an exception if the conversion fails or there is no custom class
        %   that supports this Name.  This method does not work on heterogeneous
        %   arrays: all members of FIELDS must be the same class.
        %
        %   VALUES is normally an array of values containing all the results of
        %   converting FIELDS.  It is not necessarily the same length as FIELDS
        %   because the convert method for some individual fields may return an array
        %   of values.  If all converted values are not of the same type, VALUES is a
        %   cell array of the values.
        %
        %   See displaySubclasses for the list of fields supporting convert.  Use the
        %   parse method to process header fields for which there is no convert
        %   method.
        %
        % See also Name, Value, parse, convertLike, displaySubclasses

        %   Subclass authors: you must override this to implement custom parsing of
        %   the header fields you support to return a value of the desired type, or to
        %   convert the value using array or struct delimiters other than the default
        %   (comma and semicolon).  Your convert method must not call this convert
        %   method.
            assert(strcmp(class(obj), 'matlab.net.http.HeaderField')); %#ok<STISA> need to check exact class, not subclass
            value = convertInternal(obj, [], varargin{:});
        end
    end
    
    methods (Sealed)
        % Methods sealed so that they work on heterogeneous arrays
        
        function tf = eq(obj,other)
        % == Compare two HeaderField arrays 
        %   A == B does an element by element comparison of two HeaderField arrays,
        %   returning an array of logicals indicating matching elements.  The arrays
        %   must have the same dimensions, unless one is a scalar.
        %
        %   Two HeaderFields are considered equal if they are functionally
        %   equivalent, even if not identical.  This means their Names match using a
        %   case-insensitive compare, and their Values match based on using isequal
        %   on the result of convert(), if convert() is supported for the HeaderField
        %   type.  If convert() is not supported, comparisons are based on a
        %   case-sensitive match of the Value strings.
        %
        %   This comparison only uses the Name and Value properties and ignores the
        %   actual classes of A and B, as long as both are instances of HeaderField.
        %   For example, the last line below evaluates to true, even though one is
        %   a HeaderField and the other is a DateField, because their values are the
        %   same and DateField.Name is always 'Date'.
        %
        %      import matlab.net.http.HeaderField
        %      dt = datetime('now');
        %      HeaderField('date', dt) == matlab.net.http.field.DateField(dt)
        %
        % See also Name, Value, matlab.net.http.field.DateField, convert
        
            tf = isa(obj, 'matlab.net.http.HeaderField') && ...
                 isa(other, 'matlab.net.http.HeaderField');
            if tf
                % compare two strings, where [] == [] is true
                cmpi = @(a,b) (isempty(a) && isempty(b)) || ...
                             (~isempty(a) && ~isempty(b) && strcmpi(a,b));
                % return logical array comparing scalar x to each element of array y
                fun = @(x,y) arrayfun(@(y) cmpi(x.Name, y.Name) && ...
                                           cmpv(x, y), y);
                % do scalar expansion if one is a scalar and the other not 
                if isscalar(other) 
                    tf = fun(other, obj);
                elseif isscalar(obj) 
                    tf = fun(obj, other);
                elseif ndims(obj) == ndims(other) && all(size(obj) == size(other))
                    tf = arrayfun(@(x,y) x == y, obj, other);
                    if isempty(tf)
                        % this can occur if obj and other are both HeaderField.empty
                        tf = true;
                    end
                else
                    error(message('MATLAB:dimagree'));
                end
            end
            
            function tf = cmpv(a,b)
            % Compare the values of two HeaderField objects with the same name for
            % equality.  If the Value fields are not equal, use the convert() method
            % to get the values and compare the result.  
                tf = isempty(a.Value) && isempty(b.Value);
                if tf, return, end   % both empty
                tf = isempty(a.Value) == isempty(b.Value);
                if ~tf, return, end  % only one empty
                % both not empty
                tf = strtrim(a.Value) == strtrim(b.Value);
                if tf, return, end   % strings equal
                % strings not equal, call convert
                try
                    c1 = a.convert();
                    c2 = b.convert();
                    % do isequal comparison first because it works on any object
                    % type; do == in case object implements scalar expansion
                    tf = isequal(c1,c2) || all(c1 == c2);
                catch 
                    % convert() not supported or couldn't compare using isequal or eq
                    tf = false;
                end
            end
        end
        
        function tf = isequal(obj,other)
        % isequal True if two HeaderField arrays are equal
        %   The arrays must be the same size and corresponding elements must compare
        %   equal according to eq.
        %
        % See also eq.
            tf = isequal(size(obj), size(other)) && all(obj == other);
        end
        
        function value = convertLike(obj, other, varargin) 
        % convertLike Convert the value of a header field like another header field
        %   VALUES = convertLike(FIELDS, OTHER) returns the value of the field
        %   converted to a MATLAB type, using conversion rules of the field OTHER.
        %   OTHER is a string, character vector, HeaderField or meta.class identifying
        %   a custom HeaderField subclass.  This performs the same action as convert
        %   but allows you to process the value of any header field as if it were
        %   another header field.
        %
        %   Use this when you have a header field of a type for which MATLAB does not
        %   have a custom type, but which has content that can be parsed by one of the
        %   custom types.  For example, suppose you receive a message that has a
        %   header field with the name 'Created-Date' whose contents is formatted like
        %   the HTTPDateField.  Since MATLAB does not recognize 'Created-Date' as a
        %   custom header field, you can write the following to retrieve a datetime
        %   from the value.
        %
        %     myField = response.getFields('Created-Date');
        %     date = myField.convertLike(?matlab.net.http.field.HTTPDateField);
        %
        %   See displaySubclasses for the list of fields supporting convertLike.
        %
        % See also convert, displaySubclasses
            if ~ischar(other)
                validateattributes(other, {'string','matlab.net.http.HeaderField','meta.class'}, {'scalar'}, 'convertLike', 'OTHER');
            end
            if isa(other, 'matlab.net.http.HeaderField')
                otherClass = metaclass(other);
                otherField = other;
            elseif isa(other, 'meta.class')
                otherClass = other;
                if ~(otherClass < ?matlab.net.http.HeaderField)
                    error(message('MATLAB:http:NotHeaderFieldSubclass', otherClass.Name));
                end
                otherField = [];
            else 
                otherArg = matlab.net.internal.getString(other, 'convertLike', ...
                                    'OTHER', false, {'meta.class','HeaderField'}); 
                validateattributes(char(otherArg), {'char'}, {'nonempty'}, 'convertLike', 'OTHER');
                otherField = [];
                otherClass = matlab.net.http.internal.PackageMonitor.getClassForName(otherArg);
                if isempty(otherClass)
                    error(message('MATLAB:http:NoCustomConversion', char(otherArg)));
                end
            end
            if isempty(otherField)
                % if we don't have a name for the other class, need to pick a supported one
                otherName = matlab.net.http.internal.PackageMonitor.getNamesForClass(otherClass);
                if isempty(otherName)
                    otherName = 'dummy';
                else
                    otherName = otherName(1);
                end
                otherField = createField(otherName);
                assert(~isempty(otherField)); % can't happen because we used supported name
            end
            value = convertInternal(obj, otherField, varargin{:});
        end
        
        function value = convertInternal(obj, field, varargin)
        % convertInternal Convert obj.Value like field.  If field is [], use the rules
        % of the custom class that implements obj.Name.  obj may be an array; returns
        % vector or cell vector.
            if isscalar(obj)
                % if field specified, copy our value into it and request convert
                % otherwise create a new field using current field's name
                if ~isempty(field)
                    field.Value = obj.Value;
                else
                    if isempty(obj.Name) || obj.Name == ''
                        error(message('MATLAB:http:NameEmpty'));
                    end
                    field = createField(obj.Name, obj.Value);
                    if isempty(field)
                        error(message('MATLAB:http:NoCustomConversion', char(obj.Name)));
                    end
                    % The call to convert below will recurse forever if field is the same class as
                    % this object.  This can only happen if a subclass of HeaderField calls
                    % HeaderField.convert, which isn't allowed.
                    assert(~strcmp(class(field),class(obj)));
                end
                value = field.convert(varargin{:});
            elseif isempty(obj)
                value = [];
            else
                convertit = @(o) convertInternal(o, field, varargin{:});
                value = arrayfun(convertit, obj, 'UniformOutput', false);
                try
                    % try putting all values into array; if fails, keep cell array
                    value = [value{:}];
                catch
                end
            end
        end
    end
    
    methods (Static, Hidden)
        function names = getSupportedNames()
        % getSupportedNames return field names that this class supports
        %   This is intended to be implemented by subclasses to declare the names of
        %   header fields the subclass supports.  Return value is a string vector.
        %
        %   Each time you set the Name or Value field of a HeaderField object
        %   (not a subclass), the set.Name and set.Value methods query this method in
        %   all the subclasses in the matlab.net.http.field package to see which ones
        %   implement the Name.  Most specific subclass implementing the Name is
        %   invoked (via a constructor call) to validate the provided Name and Value.
        %
        %   If getSupportedNames returns just a single string, indicating that the
        %   subclass supports only one Name, the set.Value and set.Name methods of
        %   HeaderField invoke the subclass constructor with just the Value. If it
        %   returns a vector, indicating that the subclass implements multiple names,
        %   the constructor is invoked with both Name and Value arguments.
        %   
        %   This method is also queried when the infrastructure forms a
        %   ReponseMessage received from a server, to populate the Header array in
        %   the ResponseMessage with appropriate subclasses of HeaderField.
        %
        %   If this method returns [] (the default) it indicates this is a support
        %   class or base class that will not be considered by the infrastructure to
        %   be an implementation of any particular field type.  Even though this base
        %   class returns [], you must still override this to return [] if your class
        %   supports any name, or a warning will be thrown.
        %
        %   Subclasses that extend other subclasses that implement this method should
        %   override this method if they don't implement all the supported names of
        %   their immediate superclass.  If they implement the same name as one or
        %   more of their superclasses, the subclass will be used for that name
        %   instead of the superclass.
            names = [];
        end
    end
    
    methods (Sealed, Static, Hidden)
        
        function field = createHeaderField(varargin)
        % createHeaderField Create the appropriate HeaderField subclass
        %  FIELD = createHeaderField(NAME,VALUE) create field with NAME and VALUE
        %   This is a factory method to create a new header field.  If NAME is not
        %   empty, it looks in the matlab.net.http.field package for a subclass of
        %   matlab.net.http.HeaderField whose getSupportedNames() method returns
        %   NAME. If so, it invokes that subclass's constructor, possibly throwing an
        %   error if VALUE is invalid for that sublcass.  If there is no subclass it
        %   invokes the base class HeaderField constructor.  NAME must be empty or a
        %   string. The type of VALUE may be any type that the constructor accepts.
        %
        %  NEWFIELD = createHeaderField(FIELD) create a possibly new field from FIELD
        %   In this usage an existing FIELD is provided.  If class(FIELD) is
        %   matlab.net.http.HeaderField, then the Name and Value of FIELD are used as
        %   above to possibly construct a new object of the appropriate subclass.
        %   Otherwise FIELD is simply returned.  
            if nargin == 1
                field = varargin{1};
                validateattributes(field, {'matlab.net.http.HeaderField'}, ...
                                   {'scalar'}, 'createHeaderField');
                if ~infield.isBaseClass()
                    return;
                else
                    name = field.Name;
                    value = field.Value;
                end
            else
                narginchk(2, 2);
                name = varargin{1};
                if ~isempty(name)
                    name = matlab.net.internal.getString(name, ...
                                      mfilename,'Name');
                end
                value = varargin{2};
            end
            field = createField(name, value); % may throw exception
            if isempty(field)
                field = matlab.net.http.HeaderField(name, value);
            end
        end
    end
    
    methods (Access = private)
        function rval = validateValue(obj, name, value)
        %   name  string object or []
        %   value whatever the user specified
        %   rval  string or []
        % Called whenever the Name or Value property in this object is set, to
        % validate and/or convert the value to an appropriate string for Value of the
        % header field with this name.  If the name and value are both nonempty and
        % this object is not a subclass, look for a subclass that implements the Name
        % and use it to validate or convert the value, and return that value.
        % Otherwise if no appropriate subclass is found use a generic converter in
        % this base class.  If this is a subclass already, we trust that the subclass
        % has overridden valueToString or scalarToString to convert the value to a
        % string, so we'll just call those functions.
            if isempty(value) && ~ischar(value) 
                % If [] nothing to validate
                rval = [];
            elseif isempty(name)
                % no name; call converter.  It will be the
                % generic converter in the base class, or a possibly custom
                % converter in the subclass.
                rval = obj.callValueToString(value);
            elseif ~obj.isBaseClass()
                % Name specified and we're a subclass
                % Make sure name is legal for this subclass
                names = obj.getSupportedNames();
                if ~isempty(names) && ~any(strcmpi(name, names))
                    error(message('MATLAB:http:IllegalNameForField', char(name), ...
                        strjoin(cellstr(names)), class(obj)));
                end
                % Name OK; call converter.  It will be the
                % generic converter in the base class, or a possibly custom
                % converter in the subclass.
                rval = obj.callValueToString(value);
            else
                % Name specified and we're the base class: see if there's a subclass
                % for this name.  If so, fetch its value (which does necessary
                % validation and conversion).
                field = createField(name, value);
                if isempty(field)
                    % no subclass, so use generic converter
                    rval = obj.callValueToString(value);
                else
                    rval = field.Value;
                end
            end
        end
        
        function errorCheck(obj, str, value)
        % Throw an appropriate error if str is [].  Intended to be called after
        % obj.scalarToString(value) to determine whether conversion succeeded.
            if ~ischar(str) && isempty(str) 
                obj.throwValueError('MATLAB:http:BadFieldValue', value);
            end
        end
        
        function tf = isBaseClass(obj)
        % True if this is the base class
            tf = metaclass(obj) ==?matlab.net.http.HeaderField;
        end
        
        function rval = callValueToString(obj, value)
        % Call valueToString(value) with zero, one or two additional arguments.
        % Behavior of this is subclass-specific, as it uses allowsStruct() and
        % allowsArray(), so it may change when a subclass is added in the future, but
        % such a change should only be to allow new types of values to be specified or
        % to more strictly enforce constraints on the syntax of the value.
            if obj.allowsStruct()
                rval = obj.valueToString(value, ', ', '; ');
            elseif obj.allowsArray()
                rval = obj.valueToString(value, ', ');
            else
                rval = obj.valueToString(value);
            end
            % Disallow non-visible characters other than space and tab, and all must
            % be ASCII.  This is defined as field-content in
            % http://tools.ietf.org/html/rfc7230#section-3.2, which is sequence of
            % VCHAR (%21-7E), SP (%20), HTAB (%11) or obs-text (%80-FF).
            badChar = regexp(rval, '[^\x11\x20-\x7e\x80-\xff]', 'match', 'once');
            if ~isempty(badChar) && ~ismissing(badChar) && badChar ~= ''
                % print illegal char in hex, as it's not likely visible
                error(message('MATLAB:http:IllegalCharInValue', ...
                              dec2hex(char(badChar),2)));
            end
        end
        
        function rval = quoteTokens(obj, value, index, fieldName, delims)
        % Quote any tokens in value, if needed.  See quoteValue() for detailed
        % description of quoting.  
        %    value - the string, converted from user input
        %    index (optional) - the index of this value in array of input values, 
        %                       always at least 1 if present.
        %    fieldName (optional) - the name of the field, if this value came from a 
        %                           struct; else [].
        %    delims (optional) - delimiters
        % This function calls out to getTokenExtents() to determine where the tokens
        % lie in the value.
            if isempty(value)
                rval = value;
                return;
            end
            if nargin < 4
                fieldName = [];
                if nargin < 3
                    index = 1;
                end
            end
            extents = obj.getTokenExtents(value, index, fieldName);
            if ~isempty(extents)
                % preallocate, assuming possible quotes but no escapes
                value = char(value); % TBD string
                rval(length(value)+2) = ' ';
                rx = 1;
                eEnd = 0;
                for i = 1 : size(extents, 1)
                    % first insert any non-token chars before token
                    eStart = extents(i,1);
                    if eStart > eEnd + 1
                        pad = value(eEnd+1:eStart-1);
                        rval(rx:rx+length(pad)-1) = pad;
                        rx = rx + length(pad);
                    end
                    eEnd = extents(i,2);
                    % inserted quoted token
                    if nargin > 4
                        val = obj.quoteValue(value(eStart:eEnd), delims);
                    else
                        val = obj.quoteValue(value(eStart:eEnd));
                    end
                    val = char(val); % TBD string
                    rval(rx:rx+length(val)-1) = val;
                    rx = rx + length(val);
                end
                % add remaining chars after token
                rval(rx:rx+length(value)-eEnd-1) = value(eEnd+1:end);
                rval = rval(1:rx-1);
            else
                rval = value;
            end
            rval = string(rval); % TBD string
        end
        
        function tf = isStringMatrix(obj, value)
        % Return true if useStringMatrix() and value is an Nx2 string matrix
            tf = obj.useStringMatrix() && isstring(value) && ismatrix(value) && ...
                 size(value,2) == 2;
        end
        
        function str = defaultConvert(obj, value)
        % defaultConvert Convert the value to a string using default conversion rules.
        %   This converts a single scalar value taken from an array or struct member
        %   provided by the user.  Caller has verified that value is a scalar or char
        %   vector.  Does not determine whether value needs to be quoted.
        %
        %   If numeric, error out if not real
        %   Convert using string (which converts numbers like num2str)
        %   If that errors, convert using char
        %   If that errors, throw error
        %   If result is not a scalar string, throw error
            if isnumeric(value)
                validateattributes(value, {'numeric'}, {'real'}, class(obj), 'Value');
                str = string(value);
            else
                e = [];
                try
                    str = string(value); % handles string, char, numbers
                catch
                    try 
                        str = string(char(value));
                    catch e
                    end
                end
                % Handle cases where value might override char or string to return something
                % unexpected, like a matrix or non-string.  Use disp to display value in
                % message.
                if ~isempty(e) || ~isscalar(str) || ~isstring(str)
                    error(message('MATLAB:http:CannotConvertToString', ...
                                  strtrim(evalc('disp(value)'))));
                end
            end
        end

    end
    
    methods (Sealed)
        function value = parse(obj,varargin)
        % parse  Parse the Value of the header field and return strings
        %   This function parses the Value according to a generic set of rules, where
        %   all the values are returned as string objects.  Use this method to
        %   process header fields for which there is no custom convert method.
        %
        %   VALUE = parse(obj)
        %     The Value of the header field is parsed according to a generic set of
        %     rules, where all the values are returned as string objects.  The Value
        %     is first parsed as a list of comma-separated strings, each of which
        %     become elements of the result vector.  Each element will be treated
        %     either a simple string, struct of semicolon-separated values, or struct
        %     of NAME=VALUE pairs. The NAME of each struct field will be converted to
        %     a valid MATLAB identifier using matlab.lang.makeValidName and
        %     matlab.lang.makeUniqueStrings to resolve duplicates.  If a struct field
        %     is a simple token (i.e., just a VALUE and not a NAME=VALUE), the name
        %     'Arg_N' will be used for the field, where N is the ordinal position of
        %     the field in the struct.
        %
        %     This function uses parsing rules based in part on sections 3.2.4-3.2.6
        %     of <a href="http://tools.ietf.org/html/rfc7230">RFC 7230</a>, and augmented to interpret multiple values, and it assumes
        %     that the field may contain multiple values (vectors) or name-value
        %     pairs (structs). Therefore, for example, if the value contains a quoted
        %     string, it is processed as a single token, where delimiters in the
        %     string become part of the string and the result is the string with
        %     quotes and backslashes of quoted pairs removed. Comments (text
        %     surrounded by open-closed parentheses) are retained (including the
        %     parentheses), but are treated as single tokens with possible escapes,
        %     similar to quoted strings.
        %
        %     If the field contains one or more comma-separated elements, none of
        %     which look like structs (i.e., have no semicolons or = signs) the VALUE
        %     returned is a vector of strings.  If any of the values look like
        %     structs, VALUE is an array of structs.
        %
        %     If the input is a vector of HeaderFields, this method concatentates the
        %     results of parsing each of the fields into a single array.  This could
        %     result in a cell array if the values are not of the same type.
        %
        %   VALUE = parse(obj, FIELDS)
        %     This lets you specify the names of struct fields to be created that are
        %     not named. FIELDS is a string vector, char vector or cell array of char
        %     vectors.  If the Nth field of a struct has no name, and corresponding
        %     Nth name in FIELDS exists and is nonempty, it will be used instead of
        %     Arg_N.  Using this syntax forces the returned value to be a struct (or
        %     vector of them) with at least as many fields as the length of FIELDS.
        %     Typically this pattern occurs in header fields that begin with a token
        %     followed by attribute pairs.  For example the Content-Type field has a
        %     value with the syntax:
        %
        %         media-type name1=value1; name2=value2; ... 
        %
        %     where "media-type" consists of a token whose field name would normally
        %     be Arg_1.  If you want that field to be called "MediaType", you can
        %     specify:
        %           headerField.parse('MediaType');
        %
        %   VALUE = parse(___, PARAM1, VALUE1, ... , PARAMn, VALUEn)
        %     Specifies the delimiters to use for arrays and structs, instead of
        %     comma and semicolon.  Valid parameter names are:
        %        'ArrayDelimiters'   specifies delimiters separating array elements
        %        'MemberDelimiters'  specifies delimiters separating struct fields
        %     The parameter name is case-insensitive, but the full word must be
        %     specified.  The VALUE of a PARAM is a string vector, char vector or
        %     cell vector of regular expressions specifying the possible delimiters,
        %     interpreted in the order they appear in the vector.  Specify '' if you
        %     do not want this field to be parsed as an array or struct, but you
        %     still want quoted string and escape processing.  Specify [] if you do
        %     not want the field to be parsed as an array or struct and also do not
        %     want quoted string or escape processing within that array element or
        %     struct value.
        %
        %   As an example, with no optional parameters, the following field value:
        %
        %      text/plain;q=0.5;format=flowed, text/*;level=1, image/jpeg
        %
        %   will be interpreted as an returned as an array of 3 structs:
        %
        %      Arg_1:   'text/plain'  'text/*'   'image/jpeg'
        %          q:   '0.5'         []         []
        %     format:   'flowed'      []         []
        %      level:   []            '1'        []
        %
        %   The empty fields were added because a struct array must have the same
        %   fields in all elements.
        %
        %   As another example, consider the header field:
        %
        %     Server: CERN/3.0  libmww/2.17   foo
        %
        %   The following:
        %
        %     VALUE = parse(obj, {'product','comment'}, 'ArrayDelimiters', '\s', 
        %                                                  'MemberDelimiters', '/')
        %   returns an array of 3 structs:
        %
        %     product:   'CERN'    'libmww'   'foo'
        %     comment:   '3.0'     '2.17'     []
        %  
        %   To obtain the value as a cell array of strings without interpreting the
        %   strings as possible structs:
        %
        %    VALUE = parse(obj, 'ArrayDelimiters', '\s', 'MemberDelimiters', [])
        %
        %   returns:
        %
        %    ["CERN/3.0", "libmww/2.17", "foo"]
        %
        % See also Name, Value, matlab.lang.makeValidName, convert
        % matlab.lang.makeUniqueStrings
        
        % Internal behavior, for subclass designers:
        %   An additional PARAM,VALUE pair is supported:
        %      '_custom'  true if we should invoke subclass-specific behavior to parse
        %                 this field, by invoking functions such as allowsArray() or
        %                 allowsStruct() that subclasses may override.  Subclasses
        %                 should set this property to invoke that behavior.  Default
        %                 is false.
            import matlab.net.internal.*
            skip = false;
            custom = false;
            % This argument parser actually allows the FIELDS parameter to be in any
            % position
            persistent publicParams params paramsStr
            if isempty(params)
                publicParams = {'arraydelimiters','memberdelimiters'};
                params = [publicParams '_custom'];
                paramsStr = string(params);
            end           
            for i = 1 : length(varargin)
                if skip
                    skip = false;
                    continue;
                end
                arg = varargin{i};
                if iscell(arg) || (isstring(arg) && ~isscalar(arg)) 
                    % If array of strings, it's structFields
                    if exist('structFields','var')
                        error(message('MATLAB:http:DuplicateStructFields'));
                    end
                    structFields = getStringVector(arg, mfilename, 'FIELDS', true);
                else
                    % Not array of strings; must be single string.  Need to disambiguate between a
                    % named parameter or a 1-element FIELDS array that happens to have the name of
                    % a named parameter.
                    strarg = getString(arg, class(obj), 'FIELDS');
                    % If we already have FIELDS, assume named parameter if it's not the last
                    % argument. 
                    lastarg = length(varargin) == i;
                    if ~lastarg
                        nextarg = varargin{i+1};
                    end
                    isnamed = exist('structFields','var') && ~lastarg;
                    %  ~isnamed at this point says it might be FIELDS.
                    if ~isnamed && ~lastarg
                        % Might be FIELDS and there is another argument. Assume it's FIELDS if the
                        % next argument is a named parameter. otherwise assume (for now) this is a
                        % named parameter.  Arbitrarily assume that the next argument is a named
                        % parameter only if has at least 5 chars.  If wrong, we'll still use that as a
                        % named parameter if the current argument isn't one.
                        nextarg = varargin{i+1};
                        isnamed = ~((isstring(nextarg) || ischar(nextarg)) && ...
                                    strlength(string(nextarg)) > 5 && ...
                                    any(paramsStr.startsWith(lower(nextarg))));
                    end
                    if isnamed
                        % come here if named parameter is the only valid option or if
                        % next argument isn't a named parameter
                        assert(~lastarg); % impossible due to above
                        e = [];
                        try
                            % check for named parameter but don't error out if it's not in this list
                            strarg = validatestring(strarg, params);
                        catch e
                        end
                        if isempty(e)
                            % named parameter found
                            switch strarg
                                case 'arraydelimiters'
                                    if ~isempty(nextarg)
                                        nextarg = getStringVector(nextarg, class(obj), arg);
                                    end
                                    arrayDelims = nextarg;
                                    skip = true;
                                case 'memberdelimiters'
                                    if ~isempty(nextarg)
                                        nextarg = getStringVector(nextarg, class(obj), arg);
                                    end
                                    structDelims = nextarg;
                                    skip = true;
                                case '_custom'
                                    validateattributes(nextarg, {'logical'}, {'scalar'}, 'HeaderField.parse', arg);
                                    custom = nextarg;
                                    skip = true;
                            end
                        else
                            % If not a named parameter, it's an error if we already have a FIELDS
                            % argument.  If not, assume it's FIELDS and not a named parameter.
                            if exist('structFields','var')
                                % for purposes of the message, only list the publicParams
                                validatestring(strarg, publicParams, mfilename);
                            else
                                structFields = strarg;
                            end
                        end
                    else
                        % If not a named parameter, assume FIELDS
                        if exist('structFields','var')
                            validatestring(strarg, publicParams, mfilename);
                            error(message('MATLAB:http:MissingValueForParam', char(arg)));
                        end
                        structFields = strarg;
                    end
                end
            end
            if ~exist('arrayDelims','var')
                arrayDelims = ',';
            end
            if exist('structFields','var')
                % After removing empty placeholders, check for illegal or duplicate names.  
                sf = structFields(structFields ~= ''); 
                invalid = find(arrayfun(@(s) ~isvarname(char(s)), sf), 1);
                if ~isempty(invalid)
                    error(message('MATLAB:http:InvalidFieldName', char(sf(invalid))));
                end
                if length(unique(sf)) ~= length(sf)
                    error(message('MATLAB:http:NamesNotUnique', char(strjoin('"' + sf + '"'))));
                end
            end
            if exist('structDelims','var')
                % these always return struct
                if exist('structFields','var')
                    value = parseField(obj, arrayDelims, structDelims, structFields, custom);
                else
                    value = parseField(obj, arrayDelims, structDelims, custom);
                end
            else
                if exist('structFields','var')
                    % this forces return of struct
                    value = parseField(obj, arrayDelims, ';', structFields, custom);
                else
                    % this possibly returns plain string(s) if no semicolons or equals
                    value = parseField(obj, arrayDelims, custom);
                end
            end
        end
        
        function str = string(obj)
        % string Return header field as a string
        %   STR = string(fields) returns the array of HeaderField objects as a string,
        %     as it would appear in a message, with newlines inserted between the
        %     fields but not at the end of all the fields.
            if ~isscalar(obj)
                if isempty(obj)
                    str = '';
                else
                    strs = arrayfun(@string, obj, 'UniformOutput', false);
                    str = strjoin([strs{:}], sprintf('\n'));
                end
            else
                % a single obj gets no newline
                if isempty(obj.Value)
                    v = '';
                else
                    v = obj.Value;
                end
                if isempty(obj.Name)
                    n = string('');
                else
                    n = obj.Name;
                end
                str = n + ': ' + v;
            end
        end
        
        function str = char(obj)
        % char returns the header field array as a character vector.
        %   For more information see string.
        %
        % See also string.
            str = char(string(obj));
        end
        
    end
    
    methods (Sealed, Access=protected, Hidden)
        function value = parseField(obj, varargin)
        % Parse and return the value of this header field
        % VALUE = parseField(HEADERFIELD, arrayDelims, structDelims, structFields, custom)
        %    This function is called by parse() to parse the Value property in the
        %    header as an array of strings, where each string may be interpreted as a 
        %    struct, as described in parse.  HEADERFIELD is a scalar or vector of
        %    HeaderFields.
        %
        %    arrayDelims   - string or cell array of regular expressions specifying 
        %                    delimiters between array elements.  If missing, ',' is
        %                    used.  If '' or allowsArray is overridden to return
        %                    false, the value is processed as a single array element.
        %                    Quoted string and escape processing is applied to array
        %                    elements.  If an empty array ([]), the Value is not
        %                    processed as an array (i.e., no quoted string or escape
        %                    processing is done, and this function returns either a
        %                    scalar struct or a string).  Optional whitespace on
        %                    either side of an arrayDelim is allowed and ignored.
        %    structDelims  - optional; passed to parseElement for each array element.
        %    structFields  - optional; passed to parseElement for each array element.
        %    custom        - optional: true to use subclass-specific behavior to parse
        %                    the field; false otherwise.  Default is true, which is
        %                    what most subclasses will want if they call this method.
        %                    If false the default values of all overridden functions
        %                    such as allowsArray() will be used.  If present this
        %                    parameter must be last.
        %
        %    This function parses the Value in this header field, breaking it up into
        %    array elements (strings) as specified by arrayDelims, and passes each
        %    element to parseElement (along with the structDelims and structFields
        %    parameters) for further processing.  The return values from parseElement
        %    are assembled into either a cell array of strings or array of objects,
        %    depending on the types of values returned, or 3-D array of strings if
        %    useStringMatrix() is set.
        %
        %    This function treats strings surrounded by quotes or open-closed
        %    parentheses as tokens, so will ignore arrayDelims or structDelims within
        %    them.
        %
        %    If parseElement returns only strings, this function returns a cellstr
        %    (same as if structDelims was specified as []).
        %
        %    If custom is false or useStringMatrix() is not set, and parseElement
        %    returns structs or structs and strings, this function returns an vector
        %    of structs with fields that are the union of all struct fields returned
        %    by parseElement, where unset fields have empty values and strings become
        %    structs with a single field whose name is 'Arg_1' or (if specified)
        %    structFields{1}. The combination of structs and strings results only if
        %    structDelims is missing.
        %    
        %    If custom is true and useStringMatrix is set, and parseElement returns
        %    Nx2 string matrices:
        %      Pad each Nx2 matrix with rows so their first dimensions are all the
        %      same.  Padding uses empty strings.
        %      Return an MxNx2 array, where M is number of elements.
        %
        %    If the input is a vector, this method behaves as if all the values were
        %    in one field separated by an ArrayDelims.  In other words, it
        %    concatenates the result of parsing each of the HeaderFields into single
        %    vector or cell vector if the results are heterogeneous.
        %
        %    Subclasses that override convert may use this as a utility to parse the
        %    header field with specific array or struct delimiters.
        %
        % VALUE = parseField(HEADERFIELD, arrayDelims, PARSER)
        %    Splits the Value of the header into elements at the delimiters specified
        %    in arrayDelims and pass each element to PARSER for processing.  PARSER
        %    is a handle to a function that takes a string and returns either a
        %    struct, string or scalar value or object.  This usage is for the benefit
        %    of subclasses that override convert, which want generic escape and array
        %    processing for the field but with a custom parser for the element
        %    values.  This usage always invokes subclass-specific behavior.
        %
        %    This function assembles the values received from PARSER into an array or
        %    cell array, depending on the types of values received:
        %
        %       If all values are strings or structs, returned value is as described
        %       for the non-PARSER usage above.
        %
        %       If all values are of exactly the same type or have a common
        %       matlab.mixin.heterogeneous type, the returned value is an array of
        %       those types.
        %
        %       If values are of different non-heterogeneous types, the returned
        %       value is a cell array of those values.
            if isempty(obj)
                value = obj.callParseField([]);
            else
                for i = 1 : length(obj)
                    fValue = obj(i).callParseField(obj(i).Value, varargin{:});
                    if i == 1
                        value = fValue;
                    else
                        % append the individual results to the end of the value array
                        if iscell(value)
                            value = [value num2cell(fValue)]; %#ok<AGROW>
                        else
                            try
                                value = [value fValue]; %#ok<AGROW>
                            catch
                                % above can fail if previous value and fValue are not
                                % matlab.mixin.Heterogeneous.  In that case, convert
                                % value into cell array.
                                c1 = num2cell(value);
                                c2 = num2cell(fValue);
                                value = [c1(:)' c2(:)'];
                           end
                        end
                    end
                end
            end
        end
        
        function value = callParseField(obj, str, varargin)
        % VALUE = callParseField(HEADERFIELD, STR, arrayDelims, structDelims, structFields, custom)
        %    All arguments after STR are optional; placeholders required
        %   Same as parseField but parses str instead of this field's value.
        %   Placeholder not required for custom argument.
        %
        %   obj must be a scalar.
            if isempty(obj) || isempty(obj.Value)
                value = [];
            else
                parser = @(obj, varargin) parseElement(obj, varargin{:});
                % If last argument is a logical, it's the custom parameter.
                if ~isempty(varargin) && islogical(varargin{end}) && ~varargin{end}
                    % custom = false, so pass in defaults for allowsArray and useStringMatrix
                    value = matlab.net.http.internal.fieldParser(...
                      str, obj, parser, true, false, varargin{:});
                else
                    value = matlab.net.http.internal.fieldParser(...
                      str, obj, parser, obj.allowsArray(), obj.useStringMatrix(), varargin{:});
                end
            end
        end
        
        function value = parseElement(obj, str, varargin)
        % Return the value of one array element of a header field
        %   parseElement(obj, str, structDelims, structFields, custom)
        %    This function is called by parseField to process each array element (a
        %    string) extracted from the Value of the header.
        %
        %    str          - the string to be processed
        %    structDelims - optional string or cell array of regular expressions 
        %                   specifying delimiters between struct elements.  If
        %                   missing, semicolon is used.  If an empty array, or if
        %                   missing and the string contains no semicolons, or
        %                   allowsStruct is overridden to return false, this function
        %                   returns str; otherwise it always returns a struct with at
        %                   least one field even if the string contains no
        %                   structDelims.  Optional whitespace is assumed on either
        %                   side of a delimiter, which will be ignored.
        %    structFields - optional names to use for unnamed fields (those without 
        %                   name=value syntax), as described in parse.  Ignored
        %                   if structDelims is empty.  The returned struct will
        %                   always contain at least these fields, even if the string
        %                   has fewer fields.  Unset fields will have empty values.
        %    custom       - optional true or false to implement subclass-specific
        %                   parsing based on overloaded methods such as allowsArray()
        %                   and allowsStruct().  Default is true, which is what most
        %                   subclasses will want to do.  Specifying this argument does
        %                   not require filling in placeholder for structDelims or
        %                   structFields.
        %
        %    Subclasses that override convert may use this function as a utility to
        %    parse the value as a struct, perhaps with custom delimiters or field
        %    names.  
            if isempty(obj)
                value = string.empty;
            else
                if ~isempty(varargin) && islogical(varargin{end})
                    % If last arg is a boolean, it's the custom flag.  
                    % Save it and remove the arg.
                    custom = varargin{end};
                    varargin(end) = [];
                else
                    custom = true;
                end
                if custom
                    value = matlab.net.http.internal.elementParser(...
                        str, obj.allowsStruct(), obj.useStringMatrix(), varargin{:});
                else
                    % in the non-custom case, use all the defaults
                    value = matlab.net.http.internal.elementParser(...
                        str, true, false, varargin{:});    
                end
            end
        end
        
        function exc = getValueError(obj, id, value, varargin)
        % exc = getValueError(id, value) returns an MException with the specified id
        %   Creates a message using the specified id providing these arguments:
        %      {0} Name of the field (or class if Name is empty)
        %      {1} Class of value
        %      {2} Stringified value
        %      {3...} any additional arguments from varargin
            if isstring(value) || iscellstr(value)
                v = value;
            else
                try
                    v = num2str(value);
                catch
                    try
                        v = char(value);
                    catch
                        % if it has no char or num2str method, disp it, using cellstr to
                        % remove trailing empty lines and bracketing with newlines
                        c = cellstr(evalc('disp(value)'));
                        v = sprintf('\n%s\n', c{1});
                    end
                end
            end
            % v now a string array, char array or cellstr
            name = obj.Name;
            if isempty(name)
                name = class(obj);
            end
            % if v has more than one row, concatentate with space separators
            % otherwise leave alone because cellstr strips trailing spaces
            if (iscellstr(v) && isrow(v)) || isstring(v)
                v = strjoin(v, '  ');
            end
            if ~isrow(v)
                v = strjoin(cellstr(v),'  ');
            end
            exc = MException(message(id, char(name), class(value), char(v), varargin{:}));
        end
                
        function throwValueError(obj, id, value, varargin)
        % throwValueError(id, value) throws an MException with the specified id
        %   Creates a message using the specified id providing these arguments:
        %      {0} Name of the field (or class if Name is empty)
        %      {1} Class of value
        %      {2} Stringified value
        %      {3...} any additional arguments from varargin
            throw(getValueError(obj, id, value, varargin{:}));
        end
    end
    
    methods (Static, Access=protected, Hidden)
        function converted = convertObject(~, value)
        % Converter whose sole purpose is to convert [] to an empty HeaderField array
            if isempty(value)
                converted = matlab.net.http.HeaderField.empty;
            else
                converted = []; % generates MATLAB error
            end
        end
    end        
       
    methods (Access=protected, Hidden)
        % These methods would be overidden only by classes that don't want the
        % default field constructing behavior, where the methods such as allowsArray,
        % allowsStruct and getStringException aren't sufficient to specify behavior.
        % This could be because the array or struct delimiters are something other
        % than comma and semicolon, or the default conversions from MATLAB types to
        % string aren't appropriate (or don't work).
        
        function str = valueToString(obj, value, arrayDelim, structDelim)
        % Convert the value to a string to be stored in the header.  
        %   This function is used to construct a header field value, and it accepts
        %   values that are either strings or objects that can be converted to
        %   strings.
        %
        %   It called by set.Value to convert the provided value to a string.  This
        %   base class method accepts a char matrix, cell array of strings, struct
        %   array, or array of any type whose elements can be converted by
        %   defaultConvert().  It produces a list of tokens (strings) for each
        %   element of the value array or row of a char matrix, using arrayDelim as
        %   separator between tokens and structDelim for separator between struct
        %   fields.  If an element of the value is a string and getStringException
        %   returns [] to indicate the string is valid, the element becomes the token
        %   unchanged; otherwise the token is obtained by calling scalarToString to
        %   convert the element, passing in structDelim as a separator in case the
        %   value is a struct.  In summary:
        %   
        %   Input: string
        %   Returns: the same string (unchanged)
        %
        %   Input: v1 or [v1 v2 v3] or {v1 v2 v3} 
        %   Returns: [scalarToString(v1) arrayDelim scalarToString(v2) arrayDelim ...] 
        %
        %   Both arrayDelim and structDelim are optional; the default values are
        %   comma and semicolon, respectively.
        %
        %   In this base class, if the value is a single string (not cell array), it
        %   (or the result of scalarToString) is returned unchanged.  In all other
        %   cases of string or scalar elements (but not structs) each token is
        %   further processed by quoteValue to add double-quotes and escapes in
        %   strings that have characters not allowed in tokens.  Thus, if you have a
        %   value and don't want this quote processing, convert the value to a single
        %   string before storing it in the Value.  If you do want quote processing,
        %   and have just a single string, place it in a cell array of length 1.  If
        %   the input is a struct array, quote processing is not done.
        %
        %   Subclasses may override this if they want to convert array values
        %   differently or to specify array delimiters other than the default, but
        %   they must obey the contract of this method that if the input is a single
        %   string that is valid, the result is the same string.  If the need is to
        %   use default array processing but only to control processing of individual
        %   array elements, subclasses should override scalarToString and/or
        %   getStringException instead.  If the need is only to disallow values that
        %   are arrays or structs, but not implement special processing, override
        %   allowsArray or allowsStruct.  If you do override this, it is up to you to
        %   check that the value is not an array or struct, if not allowed.
        %
        %   Subclasses can expect that this function is called exactly once each time 
        %   the value is set.  Implementations that override this function must not
        %   assume that the header's Name field is set -- processing should depend
        %   only on arguments passed into this function.
        %
        %   Subclasses overriding this method will not be passed a structDelim
        %   parameter if allowsStruct returns false, or an arrayDelim parameter if
        %   allowsArray and allowsStruct return false.  But if allowsStruct is true,
        %   both parameters (possibly empty) will be present, so in that case
        %   subclasses must declare them.
        %
        %   If your class does not allow array values (allowsArray is false), you
        %   should call throwValueError('MATLAB:http:ArraysNotAllowed') if the input
        %   parameter is not a scalar, unless your intent is to create a
        %   single-valued string out of the array.
        %
        %   Typical pattern:
        %
        %     function str = scalarToString(obj, value, varargin)
        %         if isa(value, 'matlab.net.URI')
        %             str = string(value);
        %         elseif isstring(value)
        %             str = scalarToString@matlab.net.http.HeaderField(obj, value, varargin{:});
        %         else
        %             % not a string or URI: this produces guaranteed error
        %             validateattributes(value, {'matlab.net.URI', 'string'}, {}, ...
        %                 class(obj), 'URI');
        %         end
        %     end
        
            if nargin < 4 && obj.allowsStruct()
                structDelim = string('; ');
            elseif nargin >= 4 && ischar(structDelim)
                structDelim = string(structDelim); 
            end
            if nargin < 3 
                if obj.allowsArray()
                    arrayDelim = string(', ');
                elseif obj.allowsStruct()
                    arrayDelim = [];
                end
            elseif ischar(arrayDelim)
                arrayDelim = string(arrayDelim);
            end
            if isscalar(value) && ~ischar(value) && ~isstring(value)
                % a scalar represents one element
                if isstruct(value)
                    % If a struct, convert it to string.  Any quoting must be done
                    % within scalarToString
                    if obj.allowsStruct()
                        str = obj.scalarToString(value, [], 1, [], structDelim);
                    elseif obj.allowsArray()
                        str = obj.scalarToString(value, [], 1);
                    else
                        str = obj.scalarToString(value, []);
                    end
                else
                    % Non-structs get their value quoted only if the value allows
                    % structs and the value contains an arrayDelim or structDelim,
                    % or it allows array and the value contains arrayDelim.
                    if obj.allowsStruct()
                        % if struct alloweds, quote both array and struct delims
                        str = obj.quoteTokens(obj.scalarToString(value, [], 1, [], ...
                                             [arrayDelim structDelim]));
                    elseif obj.allowsArray()
                        % of arrays allowed, quote array delims
                        str = obj.quoteValue(obj.scalarToString(value, [], 1, ...
                                             arrayDelim));
                    else
                        % If no arrays or structs allowed, don't quote anything
                        str = obj.scalarToString(value, []);
                    end
                end
                obj.errorCheck(str, value);
            elseif (ischar(value) && isrow(value)) || ...
                    isstring(value) && isscalar(value)
                % A scalar string is treated as a raw header field value that might
                % contain an arrayDelim-separated list of values beyond what
                % scalarToString can process.  Our job here is just to validate that
                % the string is OK, not to change it.
                value = string(value);
                e = obj.getStringException(value);
                if ~isempty(e)
                    % String wasn't acceptable; maybe input was an array of
                    % acceptable strings
                    if obj.allowsArray()
                        % If arrays allowed, split into elements at arrayDelim and
                        % process each through scalarToString, and then
                        % reconcatenate.  This gives scalarToString a chance to
                        % validate and possibly convert the strings. When splitting
                        % at arrayDelim, replace spaces with zero or more whitespace
                        % chars.
                        delim = regexprep(arrayDelim, ' +', '\\s*');
                        splitValue = matlab.net.http.internal.delimSplit(...
                                                       value, delim);
                        % validate each element                           
                        empties = arrayfun(@(v) ...
                                  isempty(obj.scalarToString(v, e, 1)), splitValue);
                        if any(empties) 
                            err = [];
                        else
                            err = '';
                        end
                    else
                        % validate the whole string
                        err = obj.scalarToString(value, e);
                    end
                    obj.errorCheck(err, value);
                end
                str = value; % string OK; return it
            elseif ~obj.allowsArray()
                obj.throwValueError('MATLAB:http:ArraysNotAllowed',value);
            % Everything past here is an array of more than one element
            elseif ischar(value) && ismatrix(value)
                % a char matrix with 2 or more rows; each row is a value
                numrows = size(value);
                str = string('');
                idx = 1;
                charDelim = char(arrayDelim);
                for i = 1 : numrows
                    if i ~= 1
                        str{1}(idx:idx+length(charDelim)-1) = charDelim; 
                        idx = idx+2;
                    end
                    row = value(i,:);
                    e = obj.getStringException(row);
                    if isempty(e)
                        val = row;
                    else
                        val = obj.scalarToString(row, e, i);
                        obj.errorCheck(val, row);
                    end
                    % quote processing on each row
                    if obj.allowsArray()
                        val = obj.quoteTokens(val, i, [], arrayDelim);
                    else
                        val = obj.quoteTokens(val, i, []);
                    end
                    len = strlength(val);
                    str{1}(idx:idx+len-1) = char(val);
                    idx = idx+len;
                end
                str{1}(idx:end) = [];
            elseif isvector(value)
                % it's a vector; accept either cell vector or regular vector
                values(length(value)) = string('');
                for i = 1 : length(value)
                    if iscell(value)
                        valin = value{i};
                    else 
                        valin = value(i);
                    end
                    if ((ischar(valin) && isrow(valin)) || ...
                        (isstring(valin) && isscalar(valin))) ...
                        && isempty(obj.getStringException(valin))
                        % element is a string
                        val = string(valin);
                    else
                        % element is not a string: convert it to string, using
                        % structDelim if struct is allowed
                        if obj.allowsStruct()
                            val = obj.scalarToString(valin, [], i, arrayDelim, structDelim);
                        else
                            val = obj.scalarToString(valin, [], i);
                        end
                        obj.errorCheck(val, valin);
                    end
                    if ~isstruct(valin)
                        % quote processing on non-structs
                        if obj.allowsArray()
                            values(i) = obj.quoteTokens(val, i, [], arrayDelim);
                        else
                            values(i) = obj.quoteTokens(val, i, []);
                        end
                    else
                        values(i) = val;
                    end
                end
                str = strjoin(values, arrayDelim);
            else
                obj.throwValueError('MATLAB:http:ValueNotVector',value);
            end
            if isempty(str) && ~isstring(str)
                obj.throwValueError('MATLAB:http:BadFieldValue',value);
            end
        end
        
        function str = scalarToString(obj, value, exc, index, arrayDelim, structDelim)
        % Convert the scalar value or string to a header field value
        %   This function is called by valueToString to convert a single element of
        %   an array used to set the Value in the field.  It is invoked only for
        %   scalars or strings.  For strings, it is invoked only if
        %   getStringException() returns nonempty (indicating the string as provided
        %   needs to be converted or is invalid).  Implementations of this method can
        %   assume that if value is a string, it is invalid or needs conversion.
        %
        %   value                  - The scalar value to convert
        %   exc                    - The MException returned from getStringException
        %                            Set only if value is a string or char vector.
        %                            May be false if the string is needs processing.
        %   index (optional)       - the position of the scalar in the array, if 
        %                            arrays are allowed.  Not present if
        %                            allowsArray() is false.
        %   arrayDelim (optional)  - Not present if allowsArray() is false.
        %   structDelim (optional) - Not present if allowsStruct() is false.
        %
        %   By default, throws exc if the value is a scalar string (since this
        %   means getStringException has already rejected it). Overriding this method
        %   to process a scalar string makes sense if you want to convert the input
        %   string to a different string, rather than allowing the literal string to
        %   be used.
        %
        %   If the value is not a struct, converts value using defaultConvert().
        %
        %   If the value is a struct (or, if useStringMatrix is set, an Nx2 string
        %   matrix), returns a structDelim-separated list of name=value pairs in the
        %   order they appear in the structure (or matrix), except that struct fields
        %   with names of the form 'Arg_N' (or the N,1 element of the string matrix
        %   is an empty string) are inserted with no names at the position indicated
        %   by the number N (or at the end if the name is Arg_End), and empty fields
        %   are skipped.  Nonempty fields of the struct must be strings or scalars.
        %   Each value is processed by quoteValue to quote strings with array or
        %   struct delimiters.
        %
        %   If the value is not a struct, the caller (valueToString, in the default
        %   case) is responsible for deciding whether to quote delimiters.
        %
        %   For example, given ';' as structDelim, the struct:
        %
        %     Arg_2:   'value2'
        %     Foo:     'value1'
        %     Any:     []
        %     Arg_End: '(com)ment)'
        %     Bar:     'valu;"e3'
        %
        %   returns:
        %
        %     Foo=value1;value2;Bar="valu;\"e3";(com\)ment)
        %
        %   If the input value is invalid, this function either throws an error or
        %   returns [].  If this returns [], and the caller is valueToString, a
        %   generic error will be produced that refers to the class and name of the
        %   field.  Note that an empty string, '', is a valid return value which does
        %   not signal an error.
        %
        %   Subclasses should override this to provide custom conversions for scalar
        %   or struct values, or to control the value of structDelim.  Since, if the
        %   value is a string, this is called only after getStringException() has
        %   returned false or an MException, subclasses that don't want to convert
        %   the string should throw an appropriate error (explicitly or by calling
        %   this superclass method to return the exception received from
        %   getStringException()) or return [] on any string to return a generic
        %   error.  For other types, if conversion fails, subclasses should throw an
        %   error or return []. Subclasses overriding this method will not be passed
        %   a structDelim argument if allowsStruct is false.
        %
        %   If you override this method, it is safest to declare it this way:
        %
        %     function str = scalarToString(obj, value, varargin)
        %
        %   and pick off the additional arguments from varargin, if they are present.
        %
        %   The result returned by this function will be escaped and quoted, if
        %   necessary.  If you have already quoted and escaped your string, override
        %   getTokenExtents() to return [].
        
            if nargin < 5
                structDelim = [];
                if nargin < 4
                   index = 1;
                end
            end
            if ischar(value) || isstring(value)  % it has to be a simple string
                % The simple string case is always an error
                if isempty(exc)
                    % We get here if caller invoked us on a string without setting
                    % exception. In that case throw a generic exception.
                    throwValueError(obj, 'MATLAB:http:BadFieldValue', value);
                else
                    throw(exc);
                end
            elseif isstruct(value) || obj.isStringMatrix(value)
                % TBD this needs cleanup for the isStringMatrix case
                if obj.allowsStruct()
                    if obj.useStringMatrix() 
                        fields = cellstr(value(:,1));
                    else
                        fields = fieldnames(value);
                    end
                    lf = length(fields);
                    strs = strings(1,lf);
                    argStrs = strings(1,lf);
                    for i = 1 : lf
                        % for each field of the struct, insert an element in strs
                        % that's either the value as a string or name=value
                        ns = fields{i};
                        if obj.useStringMatrix()
                            v = value(i,2);
                        else
                            v = value.(ns);
                        end
                        % convert the value and add quotes if it needs it
                        v = obj.defaultConvert(v);
                        v = obj.quoteTokens(v, index, ns, [arrayDelim structDelim]);
                        if nargin < 4
                            structDelim = string(';');
                        end
                        % checkValueForDelim(v, {arrayDelim structDelim});
                        if obj.useStringMatrix()
                            idx = 0;
                        else
                            idx = strfind(ns, 'Arg_'); 
                        end
                        if isempty(ns) || ...
                           (~isempty(idx) && idx == 1 && ...
                                ~isempty(regexp(ns(5:end),'^[1-9]\d*$', 'once')))
                            % If a field name begins with Arg_ and followed by a
                            % number N, or is empty, put just the value (not the
                            % name) in the N'th position argStrs.  Don't have to
                            % worry about N being used twice because field names
                            % can't repeat. 
                            if idx ~= 0
                                argno = str2double(ns(5:end));
                            else
                                argno = i;
                            end
                            argStrs(argno) = v;
                        else
                            strs(i) = string(ns) + '=' + v; 
                        end
                    end
                    % for each nonempty string in argstrs, insert it into its proper
                    % place in strs
                    for i = 1 : length(argStrs)
                        arg = argStrs(i);
                        if ~isempty(arg)
                            strs = [strs(1:i-1) arg strs(i:end)];
                        end
                    end
                    strs(strs=='') = []; % remove empty elements
                    str = strjoin(strs, structDelim); 
                else
                    obj.throwValueError('MATLAB:http:NoStructs', value);
                end
            else
                str = obj.defaultConvert(value);
            end
        end
        
        function exc = getStringException(~, ~)
        % Determine validity of input string for the field
        %   exc = getStringException(obj, str) returns empty if the string is valid
        %     for use in the field as is.  Returns an MException (not thrown) if the
        %     string is invalid.  Returns false if the string needs to be further
        %     processed by scalarToString to convert to a field value or throw an
        %     error.  Note the MException is returned, not thrown.
        %
        %   This function, intended to be overridden by subclasses, is invoked by
        %   valueToString on each string, string in a cell array, or row of a char
        %   matrix to set the Value field.  If this returns [], valueToString uses
        %   the string as is.  If this returns false or an MException, valueToString
        %   will invoke scalarToString to try to convert the value to an acceptable
        %   string.  The default scalarToString will throw the MException if it is
        %   passed a string.  If you override this, throw an MException only if you
        %   don't intend to override scalarToString to further process the string.
        %
        %   If you want to throw a custom error for an invalid string, return an
        %   MException.  If you override scalarToString, check if a string is passed
        %   in.  If so, either throw a custom error or return [] to throw a generic
        %   error.
        %
        %   Default behavior of this function returns [], which says any string is
        %   acceptable.  Subclasses that want to validate strings should implement
        %   a pattern something like this:
        %    
        %    try
        %       convert str to expected object type that would be returned by
        %       convert() or parse it, for e.g. 
        %           v = matlab.net.http.internal.elementParser(str, true, true); 
        %       if v is valid exc = [] 
        %       if invalid exc = exception or false
        %    catch exc
        %    end
            exc = [];
        end
        
    end
    
    methods (Static, Access=protected, Hidden)
        % These methods should be overridden by most subclasses to customize value
        % conversions.
        
        function tf = allowsArray()
        % Return true if this header field allows or contains lists or arrays
        %   This function, intended to be possibly overridden by subclasses, is
        %   invoked by methods in this class that store the Value field to determine
        %   whether array processing should be done. The default is true.
        %
        %   If false, attempt to set the Value property of this field to a non-scalar
        %   results in an error, and the parse function will not attempt to
        %   parse the Value as a list.
            tf = true;
        end
        
        function tf = allowsStruct() 
        % Return true if this header field allows or contains structs
        %   This function, intended to be possibly overridden by subclasses, is
        %   invoked by methods in this class that store or return the Value field to
        %   determine whether struct processing should be done.  The default is true.
        %
        %   If false, attempt to set the Value property of this field to a struct
        %   results in an error, and the parse function will not attempt to
        %   parse the value as a struct.
            tf = true;
        end
        
        function tf = useStringMatrix()
        % If true and allowsStruct is set, the name=value parameters in an element of
        %   this field should be returned in an Nx2 string matrix instead of a
        %   struct, and likewise an Nx2 string matrix is accepted to set the value
        %   instead of a struct.  By default this is false.  It is overridden by
        %   subclasses that return and accept field values in specialized objects.
            tf = false;
        end
        
        function tf = allowsTrailingComment() 
        % Return true if this header field allows a comment at the end of the header
        %   Default is true.  If set and the header contains a comment, it may be 
        %   obtained from the Comment property.
        %   
        %   This setting applies only to comments at the ends of fields.  Subclasses
        %   that want to allow comments in particular positions inside elements of
        %   the header need to override TBD
            tf = true;
        end
        
        function value = getDefaultValue
        % Return the default value for this field.  This is the value that will be
        %   stored in the field if it is set to [] or not specified in the
        %   constructor.  Default returns [].  Subclasses need override this only if
        %   they want to specify a different default.  The value returned must be a
        %   legal value acceptable to the set.Value function.
            value = [];
        end
        
        function tokens = getTokenExtents(value, index, field) %#ok<INUSD>
        % Given a string that represents the converted-to-string value of a
        % particular element of an array or struct that was passed in as a header
        % field value, return the an n-by-2 array of starting and ending token
        % positions.  These are the tokens that need to be processed for possible
        % escapes and quotes.  If this returns empty, no escape/quote processing is
        % done on the value.  This is appropriate if the tokens have already been
        % checked to be valid tokens.
        %
        % This function is needed because field values provided by the user are not
        % necessarily simple tokens, but may contain delimiters.  For example the
        % MediaType field of the AcceptField has the syntax "type/subtype" which is
        % actually 2 tokens separated by a slash.
        %   field - If not empty, the name of the struct field being processed.  
        %   index - The index of the element; always at least 1
        % Default returns [1 length(value)] indicating the entire value is a token.
        %
        % Subclasses who have already escaped and quoted their tokens in
        % scalarToString() should override this to return [] to indicate no quote
        % processing should be performed.
            tokens = [1 strlength(string(value))];
        end
        
        function str = quoteValue(token, delims)
        % Quote the token (string) if it contains a sequences that need quoting.
        %   delims - optional vector of strings to check for in addition to standard
        % This function is called by valueToString and scalarToString, before storing
        % a token, to quote (with double quotes) a token that contains unquoted
        % special characters or whitespace.  Within quotes, backslash and
        % double-quote characters are escaped with backslash.  The special characters
        % are those not allowed in tokens according to RFC 7230:
        %         (),/:;<=>?@[\]{}"  
        % plus all whitespace characters plus anything that matches one of the
        % delims. If the string is already quoted (i.e., begins and ends with paired
        % double-quotes or open-closed parentheses), the token is assumed to be
        % already formatted as a quoted string or comment and is returned unchanged.
        %
        % This method is only needed
        %
        % Subclasses that have different quoting rules may wish to override this.
        % Subclasses whose scalarToString() method has already quoted the returned
        % value, or which don't want any additional quoting of the value, should
        % override getTokenExtents() to return [].
            if isempty(token) 
                str = token;
                return;
            end
            token = string(token); 
            % See if starts with a quote character: " or (
            if strlength(token) > 1 && (token.startsWith('"') || token.startsWith('(')) 
                % It starts with a " or ( 
                % See if it ends with the paired " or )
                if token.startsWith('(')
                    match = ')';
                else
                    match = '"';
                end
                if token(end) == match
                    % ends with a " or ), but we need to see if it's properly paired
                    i = 2;
                    while i < strlength(token)-1
                        ch = extractBetween(token,i,i);
                        if ch == '\'
                            i = i + 2;
                        elseif ch == match
                            % unescaped " or ) means it's not a quoted string or
                            % comment after all
                            break;
                        else
                            i = i + 1;
                        end
                    end
                    % If the first unescaped matching quote is the last character of
                    % the token, then it's a quoted string and we just return as is.
                    % If we fall off the end of string (e.g., the last " is escaped)
                    % or we didn't get to the end, it's not a quoted string or
                    % comment.
                    if i == strlength(token)
                        % return as is
                        str = token;
                        return;
                    end
                end
            end
            
            % It's not already a quoted string, so quote if necessary
            if nargin > 1
                str = matlab.net.http.internal.quoteToken(token, delims);
            else
                str = matlab.net.http.internal.quoteToken(token);
            end
        end
        
    end
    
    methods (Static, Sealed, Hidden)
        function str = createQuotedString(value)
        % Given a string value, place double-quotes around it and add escapes
        %   Creates quoted-pair (using backslash) for double-quote and backslash.  No
        %   reason to escape other chars.
            str = ['"' regexprep(value, '(\|")', '\$1') '"'];
        end
        
        function tf = isValidToken(value, allowQuotes, allowComments)
        % Return true if string is a valid HTTP token.  This is a utility useful for
        % subclasses to validate strings to be inserted in HTTP field values.
        % Returns true if the string contains at least one character and only the
        % characters allowed in tokens as per section 3.2.6 of RFC 7230.  White space
        % is not permitted anywhere in the value (except within comments and quotes)
        %
        % The optional allowQuotes and allowComments parameters, if true, specify
        % whether quoted strings and/or comments are allowed.  In this case the token
        % is considered valid if it is completely surrounded by paired double-quotes
        % or parentheses and internal quotes or parentheses are aqpropriately
        % escaped, with no constraints on the characters within the string.
            if ~ischar(value) || ~isrow(value) || isempty(value)
                tf = false;
                return;
            end
            if (nargin > 1 && allowQuotes && value(1) == '"') || ...
               (nargin > 2 && allowComments && value(1) == '(') 
                % allowQuotes and it begins with a quote, or allowComments and it
                % begins with a left paren
                if length(value) > 1
                    if allowQuotes
                        closer = '"';
                    else
                        closer = ')';
                    end
                    % the string must end with the closer and there must be no
                    % non-escaped closers in the string
                    % remove escaped pairs
                    unescapedChars = regexprep(value(2:end), '\\.', '');
                    % the only closer left should be at the end
                    tf = strfind(unescapedChars, closer) == length(value) - 1;
                    tf = isscalar(tf) && tf;
                else
                    tf = false;
                end
            else
                % Caller doesn't allow comments or quotes, or allowed but isn't a
                % comment or quoted string, so check for any illegal characters.
                % It's worth noting that org.apache.http is much more liberal with
                % regard to what characters can occur in a token, compared to RFC
                % 7230.  It's not clear what rules POCO uses.
                tf = isempty(regexp(value, '[^' + TokenChars + ']', 'once'));
            end
        end
        
        function str = qualityToString(value)
        % Parse value and return a string representing the quality value, suitable
        % for use in a q=weight parameter of a header field.  If the value is empty,
        % return [].  If the value is a string and it is valid, just return it.
        % Otherwise if the value is a number convert it to a quality string. A valid
        % quality must be between 0 and 1 inclusive.  This method does not enforce
        % the syntax of the string, as long as it has a valid numeric value.
            isvalid = @(x)isreal(x) && isscalar(x) && x >= 0 && x <= 1;
            if isempty(value)
                str = [];
            elseif isnumeric(value)
                if isvalid(value)
                    % stringify numbers < 1 by printing '0.' and up to 3 digits with
                    % no trailing zeros
                    str = sprintf('%1.3g',round(value,3));
                else
                    error(message('MATLAB:http:BadQuality', num2str(value)));
                end
            else
                num = matlab.net.internal.getString(value, mfilename, 'quality');
                num = str2double(num);
                if isvalid(num)
                    str = value;
                else
                    error(message('MATLAB:http:BadQuality', value));
                end
            end
        end
    end
    
    methods (Static, Sealed)
        function [fields, names] = displaySubclasses()
        % displaySubclasses Display supported HeaderField subclasses
        %   HeaderField.displaySubclasses displays all subclasses of HeaderField in
        %   the matlab.net.http.field package that you can construct, along with the
        %   names of header fields they support.
        %
        %   [FIELDS, NAMES] = HeaderField.displaySubclasses returns FIELDS, an array
        %   of strings naming the subclasses, and NAMES, a cell array containing
        %   vectors of strings containing the header field names that the subclasses
        %   support: NAMES{i} contains the names supported by FIELDS(i).  NAMES{i} is
        %   empty if FIELDS(i) has no constraints on supported names.
        
            % Basically we just list all non-abstract classes in matlab.net.http.field
            % with public constructors.
            list = meta.package.fromName('matlab.net.http.field').ClassList;
            
            classes = arrayfun(@dispClass, list, 'UniformOutput', false);
            classes(cellfun(@isempty, classes)) = []; % remove empty cells
            classes = sort(classes);
            
            % this returns string vector or string.empty
            if nargout ~= 1
                getNames = @(c) string(feval(['matlab.net.http.field.' char(c) '.getSupportedNames']));
            end
            
            if nargout > 0
                fields = string(classes);
                if nargout > 1
                    names = arrayfun(getNames, fields, 'UniformOutput', false);
                end
            else
                fprintf('%s\n',getString(message('MATLAB:http:DisplaySubclasses')));
                if desktop('-inuse')
                    cellfun(@(x) fprintf('%-*s %s\n', 72+length(x), ...
                              sprintf('<a href="matlab:doc matlab.net.http.field.%s">%s</a>',x, x), ...
                              strjoin(getNames(x), ', ')), classes);
                else
                    % no links in nodesktop case
                    cellfun(@(x) fprintf('%-24s %s\n', x, ...
                                         strjoin(getNames(x), ', ')), classes);
                end
            end
            
            function clsName = dispClass(clazz)
                % get name of the class after the last '.'
                clsName = clazz.Name(regexp(clazz.Name, '\.[^.]+$')+1 : end);
                assert(~isempty(clsName));
                % find the constructor; the method with the same name
                methods = clazz.MethodList;
                constructor = methods(cellfun(@(x)strcmp(x,clsName), {methods.Name}));
                assert(isscalar(constructor));
                if ~strcmp(constructor.Access,'public') || clazz.Abstract
                    % can't create if constructor not public or class is abstract
                    clsName = [];
                end
            end
        end
    end
            
end

function obj = instantiate(metaclass, varargin)
% Instantiate the specifed class by calling its constructor with varargin parameters.
% If "too many arguments" error occurs, and varargin has 2 arguments, try calling it
% with just the 2nd argument (which is the value).
    try
        obj = feval(metaclass.Name, varargin{:});
    catch e
        if strcmp(e.identifier, 'MATLAB:maxrhs') && length(varargin) == 2
            obj = feval(metaclass.Name, varargin{2});
        else
            rethrow(e);
        end
    end
end

function field = createField(name, varargin)
% If a subclass of matlab.net.http.HeaderField in the matlab.net.http.field package
% is found that implements the field name return the field object.  Otherwise returns
% [].
    metaclass = matlab.net.http.internal.PackageMonitor.getClassForName(name);
    if isempty(metaclass)
        field = [];
    else
        % Subclass found; instantiate it. This has the side-effect of validating the
        % name and value.
        if isscalar(string(feval([metaclass.Name '.getSupportedNames'])))
            % supports just one name, so constructor has no name argument
            field = instantiate(metaclass, varargin{:});
            field.Name = name; % insure the name has the same case as what was given
        else
            field = instantiate(metaclass, name, varargin{:});
        end
    end
end