% A collection of introspection utilities

% Copyright 2011-2020 The MathWorks, Inc.

classdef ( Hidden, Sealed ) Reflection

    methods ( Static )
        function [props, useGetSet, constraints] = getAllPublicProperties( someObjOrClass )
        % Get all public properties, including public-hidden.
        % Param: someObjOrClass - a CustomPropTypes or associated metaclass
        % Return:
        % props - cellstr of property names
        % useGetSet - logical vector of whether property is PCTGetSet
        % constraints - cellstr of constraints
            [props, useGetSet, constraints] = iPropInfo( someObjOrClass, true );
        end
        function [props, useGetSet, constraints] = getPublicProperties( someObjOrClass )
        % Get all public properties, excluding public-hidden.
        % Param: someObjOrClass - a CustomPropTypes or associated metaclass
        % Return:
        % props - cellstr of property names
        % useGetSet - logical vector of whether property is PCTGetSet
        % constraints - cellstr of constraints
            [props, useGetSet, constraints] = iPropInfo( someObjOrClass, false );
        end
        function [settableProps, allprops, settablePropsUsingGetSet] = ...
                getUserSettableProperties( someObjOrClass )
        % Return all properties that a user can set, excluding public-hidden
        % Param: someObjOrClass - a CustomPropTypes or associated metaclass
        % Return:
        % settableProps - cellstr of property names
        % allprops - all known properties
        % settablePropsUsingGetSet - settableProps which are PCTGetSet
            [allprops, useGetSet, ~, publicSet] = iPropInfo( someObjOrClass, false );
            settableProps = allprops( publicSet );
            settablePropsUsingGetSet = allprops( publicSet & useGetSet );
        end
        function [settableProps, allprops, settablePropsUsingGetSet] = ...
                getAllUserSettableProperties( someObjOrClass )
        % Return all properties that a user can set, including public-hidden
        % Param: someObjOrClass - a CustomPropTypes or associated metaclass
        % Return:
        % settableProps - cellstr of property names
        % allprops - all known properties
        % settablePropsUsingGetSet - settableProps which are PCTGetSet
            [allprops, useGetSet, ~, publicSet] = iPropInfo( someObjOrClass, true );
            settableProps = allprops( publicSet );
            settablePropsUsingGetSet = allprops( publicSet & useGetSet );
        end
        function cstr = getConstraint( someObjOrClass, propName )
        % Return the constraint for a given named property on an object.
        % Param: someObjOrClass - a CustomPropTypes or associated metaclass
        % Param: propName - scalar property name
            % validateattributes( propName, {'char'}, {'row'} );
            [props, ~, constraints] = iPropInfo( someObjOrClass, true );
            idx = strcmp( propName, props );
            numMatch = sum(idx);
            if numMatch == 0
                % No such property
                error(message('parallel:internal:cluster:NoSuchProperty', propName, iClassName( someObjOrClass )));
            else
                assert( numMatch == 1 );
            end
            cstr = constraints{ idx };
        end
        function newValue = deriveAndCheckConstraint( someObj, propName, newValue )
        % Given an object, a property name and a proposed new value, derive the
        % constraint on that property and call
        % checkConstraint().
        %
        % checkConstraint requires an actual CustomPropTypes,
        % not a metaclass.
            import parallel.internal.customattr.Reflection;
            import parallel.internal.customattr.checkConstraint;
            constraint = Reflection.getConstraint( someObj, propName );
            if ~isempty( constraint )
                newValue = checkConstraint( constraint, someObj, propName, newValue );
            end
        end
        function newValue = deriveAndCheckSimpleConstraint( someObjOrClass, propName, newValue )
        % Given an object or a metaclass, a property name and a proposed new value,
        % derive the constraint on that property and call checkSimpleConstraint().
            import parallel.internal.customattr.Reflection;
            import parallel.internal.customattr.checkSimpleConstraint;
            import parallel.internal.customattr.Constraints;
            constraintAndTiming = Reflection.getConstraint( someObjOrClass, propName );
            simpleConstraint    = Constraints.parseConstraint( constraintAndTiming );
            if ~isempty( simpleConstraint )
                if isscalar(someObjOrClass)
                    newValue = checkSimpleConstraint( simpleConstraint, propName, newValue );
                else
                    assert( numel(someObjOrClass) == numel(newValue) );
                    for idx = 1:numel(someObjOrClass)
                        newValue{idx} = checkSimpleConstraint( simpleConstraint, propName, newValue{idx} );
                    end
                end
            end
        end
        function tfvec = elementwiseHasAllProperties( objs, propNames )
        % Return a logical array indicating whether each individual element of
        % "objs" has all the named properties. Helper for
        % findJob/filterJobs. Considers hidden properties so that findJob
        % &c. can find jobs by hidden properties.
            assert( iscellstr( propNames ) ); %#ok<ISCLSTR>
            % First, work out unique classes
            [~, ui, uj] = unique( arrayfun( @class, objs, 'UniformOutput', false ) );
            % Then, for the unique elements, work out if they have the properties
            doShowHidden = true;
            tfTemp = arrayfun( @(x) iHasAllProperties( x, propNames, doShowHidden ), objs( ui ) );
            % Finally, fill out the return by selecting the elements from tfTemp
            tfvec = tfTemp( uj );
        end
        function tfvec = propertyUsesGetSet( objOrClass, propNames )
        % Given a list of property names, return a logical vector indicating
        % whether a particular property is PCTGetSet
        % Param: someObjOrClass - a CustomPropTypes or associated metaclass

            [allProps, useGetSet] = iPropInfo( objOrClass, true, false );
            [isMemb, whichMemb]   = ismember( propNames, allProps );
            assert( all( isMemb ) );
            tfvec = useGetSet( whichMemb );
        end
        function [props, useGetSet, constraints] = getNonPublicProperties( objOrClass )
        % Get all non-public properties, including public-hidden.
        % Param: someObjOrClass - a CustomPropTypes or associated metaclass
        % Return:
        % props - cellstr of property names
        % useGetSet - logical vector of whether property is PCTGetSet
        % constraints - cellstr of constraints
            [props, useGetSet, constraints] = iPropInfo( objOrClass, true, false );
         end
    end

end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% iClassName - class name of an object or its metaclass
function n = iClassName( objOrMetaClass )
    if isa( objOrMetaClass, 'meta.class' )
        n = objOrMetaClass.Name;
    else
        n = class( objOrMetaClass );
    end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Used by elementwiseHasAllProperties - works out for a scalar object if it has
% all the named properties.
function tf = iHasAllProperties( obj, propNames, showHidden )
    props = iPropInfo( obj, showHidden );
    tf = isempty( setdiff( propNames, props ) );
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% iGetClassInfo - return class name and metaclass from either an existing
% metaclass or an object.
function [mc, className] = iGetClassInfo( objOrMetaClass )
    if isa( objOrMetaClass, 'parallel.internal.customattr.CustomPropTypes' )
        mc = metaclass( objOrMetaClass );
        assert( isa( mc, 'parallel.internal.customattr.MetaClass' ) );
    elseif isa( objOrMetaClass, 'parallel.internal.customattr.MetaClass' )
        mc = objOrMetaClass;
    else
        error(message('parallel:cluster:CustomPropTypesRequired'));
    end
    className = mc.Name;
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Most of the support for Reflection is here - this reflects upon a given object
% and returns:
% - a list of property names
% - vector of whether they specify PCTGetSet
% - a list of constraints
% - vector of whether they can be set publicly
function [props, useGetSet, constraints, publicSet] = iPropInfo( obj, showHidden, publicOnly )
    persistent CLASS_MAP
    if isempty( CLASS_MAP )
        CLASS_MAP = containers.Map( 'KeyType', 'char', 'ValueType', 'any' );
    end

    if nargin < 3
        publicOnly = true;
    end
    
    [mc, className] = iGetClassInfo( obj );

    if CLASS_MAP.isKey( className )
        c              = CLASS_MAP( className );
    else
        % List those properties for which we have MetaProps - this is actually *all*
        % properties.
        gotMeta        = arrayfun( @(x) isa(x,'parallel.internal.customattr.MetaProp' ), mc.PropertyList );
        % Check whether the properties are publicly accessible
        canGet         = arrayfun( @(x) isequal( x.GetAccess, 'public' ), mc.PropertyList );
        canSet         = arrayfun( @(x) isequal( x.SetAccess, 'public' ), mc.PropertyList );

        % 'public' means 'public get' essentially.
        isPublic       = canGet;

        % This is the list of all property names as a row vector.  Note that 
        % all other variables are columns.
        props          = { mc.PropertyList.Name };
        
        % Next, derive a logical flag index which indicates which properties use
        % hGetProperty. Default to false. 
        useGetSet              = false( numel( props ), 1 );
        useGetSet( gotMeta )   = [ mc.PropertyList( gotMeta ).PCTGetSet ].';

        % Extract the constraints, default to ''.
        constraints              = repmat( {''}, numel( props ), 1 );
        constraints( gotMeta )   = { mc.PropertyList( gotMeta ).PCTConstraint };

        % Also check to see if they're hidden
        hidden = [ mc.PropertyList.Hidden ]';
        
        % Stash results in the cache for later.
        c                      = { props, useGetSet, constraints, canSet, hidden, isPublic };
        CLASS_MAP( className ) = c;
    end
    hidden   = c{5};
    isPublic = c{6};
    select   = true( size( c{1} ) )';
    if ~showHidden
        select  = select & ~hidden;
    end
    if publicOnly
        select = select & isPublic;
    end

    props       = c{1}(select);
    useGetSet   = c{2}(select);
    constraints = c{3}(select);
    publicSet   = c{4}(select);
end
