=encoding utf8

=head1 NAME

Module::Generic::Scalar - String Manipulation Object Class

=head1 SYNOPSIS

    my $s = Module::Generic::Scalar->new( "John Doe" );
    print( $s->substr( 0, 4 ), "\n" );
    # prints: John
    $s->append( 'more data' );
    # $a is now a Module::Generic::Array object
    my $a = $s->as_array;
    # $bool is now a Module::Generic::Boolean object
    # with value is true or false depending on underlying value of $s
    my $bool = $s->as_boolean;
    # $a is now a Module::Generic::Number object
    my $n = $s->as_number
    # raw string
    my $str = $s->as_string;
    print( "Hello $s\n" );
    # Hello John Doe

    my $str = Module::Generic::Scalar->new();
    $a->callback( add => sub
    {
        my( $new ) = @_;
        return unless( $$new eq $approved_string );
        return(1);
    });
    $str->append( $some_string );
    # or
    $str .= $some_string;

    $s->capitalise;
    $s->chomp;
    $s->chop;
    $s->clone;
    my $crypted = $s->crypt( $salt );
    $s->defined;
    $s->empty;
    $s->fc( $that );
    my $hex = $s->hex;
    my $index = $s->index;
    $s->is_alpha;
    $s->is_alpha_numeric;
    $s->is_empty;
    $s->is_lower;
    $s->is_numeric;
    $s->is_upper;

    my $s = Module::Generic::Scalar->new( 'Jack John Paul Peter' );
    # Takes the string, split it by space (now an array), join it by comma (now a scalar) and rejoin it with more strings
    say $s->split( qr/[[:blank:]]+/ )->join( ', ' )->join( ', ', qw( Gabriel Raphael Emmanuel ) );
    # prints: Jack, John, Paul, Peter, Gabriel, Raphael, Emmanuel

    $s->lc;
    $s->lcfirst;
    $s->left;
    $s->length;
    my $s = Module::Generic::Scalar->new( "I disapprove of what you say, but I will defend to the death your right to say it" );
    print( "Matches? ", $s->like( qr/\bapprove[[:blank:]\h]+what\b/ ) ? 'yes' : 'no', "\n" ); # print yes
    if( $s->like( qr/[[:blank:]\h]+/ ) )
    {
        # Do something
    }
    # or
    if( my $rv = $s->like( qr/([[:blank:]\h]+)/ )->result )
    {
        my $first_match = $rv->capture->first;
        # Do something
    }

    $s->lower;
    $s->ltrim;
    # Remove all kind of leading whitespaces
    $s->ltrim( qr/[[:blank:]\h]+/ );

    # $s is "Hello world"
    $s->match( 'world' ); # pass
    $s->match( qr/WORLD/i ); # pass
    $s->match( 'monde' ); # obviously fail
    if( $s->match( qr/[[:blank:]\h]+/ ) )
    {
        # Do something
    }
    if( my $rv = $s->match( qr/([[:blank:]\h]+)/ )->result )
    {
        my $first_match = $rv->capture->first;
        # Do something
    }

    my $unpack = $unpack_data->unpack( "A10xA28xA8A*" )->object;
    my $fh = $s->open;
    $fh->print;
    $fh->read;
    $s->ord;
    $s->pack;
    $s->pad( 3, 'X' );
    # XXXHello world

    $s->padd( -3, 'X' );
    # Hello worldXXX
    $s->pos;
    $s->$s->prepend( join( '', qw( Cogito ergo sum ) ) );
    $s->quotemeta;

    # $s is Hello world
    my $rv = $s->replace( ' ', '_' ); # Hello_world
    my $rv = $s->replace( qr/[[:blank:]\h]+/, '_' ); # Hello_world
    if( my $rv = $s->replace( qr/([[:blank:]\h]+)/, '_' )->result )
    {
        my $first_match = $rv->capture->first;
        # Do something
    }
    $s->reset;
    $s->reverse;
    Module::Generic::Scalar->new( "Hello world" )->right( 5 );
    # will produce: world
    $s->rindex;
    # Remove all kind of trailing whitespaces
    $s->rtrim( qr/[[:blank:]\h]+/ );
    $s->scalar;
    $s->set;
    # Returns a Module::Generic::Array object
    my $a = $s->split( /\n/ );
    my $a = $s->split( qr/\n/ );
    $s->sprintf;
    $s->substr;
    $s->tr;
    $s->trim;
    $s->uc;
    $s->ucfirst;
    print( $s->defined ? 'defined' : 'undefined', "\n" );

    my $array = $s->unpack( "W" );
    # Returns the ord() value as a Module::Generic::Array object
    $s->unpack( "W" )->length;
    # Object context. Ditto
    my( $date, $desc, $income, $expend ) = $s->unpack( "A10xA27xA7A*" );
    # Returns a regular list of values
    $s->upper;

=head1 VERSION

    v1.4.4

=head1 DESCRIPTION

The purpos of this calss/package is to provide an object-oriented approach to string manipulation.

The object is overloaded, so it returns the embedded string when used as a string.

    print( "I met with $s\n" );

Would produce: I met with John Doe

=head1 METHODS

=head2 new

Provided with scalar reference, an array or just a regular string and this returns a new object.

If an array reference or an array-based object is provided like L<Module::Generic::Array>, this will concatenate all the array elements

=head2 append

Provided with a string, and this will add it to the end of the string object.

    # $s is 'Hello'
    $s->append( ' world' );
    # now this is: Hello world

=head2 as_array

Returns the current string value as an L<Module::Generic::Array> object.

=head2 as_boolean

Returns a L<Module::Generic::Boolean> object with its value set to true or false based on the value of the scalar object.

    # $s is 1
    $s->as_boolean; # sets to true
    # $s is 0
    $s->as_boolean; # sets to false
    # $s is hello
    $s->as_boolean: # sets to true
    # $s is undefined or empty
    $s->as_boolean: # sets to false
    # etc...

L<Module::Generic::Boolean> objects are very useful because they can be used in perl as o or 1 to indicate false or true, but when used in json, they are automatically converted to C<false> or C<true>

=head2 as_number

Returns the current string as an L<Module::Generic::Number> object.

=head2 as_string

Returns the object string as a string.

    my $s = Module::Generic::Scalar->new( "Mary Jane" );
    print( "Hello $s\n" );
    # Hello Mary Jane

=head2 callback

Sets or gets a callback on the scalar object. You can set 2 types of callback: C<add> or C<remove> which will be called respectively when you add something to the scalar or when you remove something from it.

It does this by tieing the scalar to its internal class C<Module::Generic::Scalar::Tie> that will call the C<add> or C<remove> callback when data is being added or removed, no matter how, i.e. be it through the methods of this class, or by directly trying to add data to it:

    my $str = Module::Generic::Scalar->new();
    $a->callback( add => sub
    {
        my( $new ) = @_;
        return unless( $$new eq $approved_string );
        return(1);
    });
    $str->append( $some_string );
    # or
    $str .= $some_string;

In both cases above the string will be added, and if it were some other unapproved text, the data would not be added to the string.

So, the callback must return true to allow the data to be added, or C<undef> to refuse. Returning an empty string or 0 will not work to be considered a rejection even though it is a false value.

Both C<add> and C<remove> callbacks receives an hash reference as its sole arguments.

The C<add> callback receives an hash reference with the following properties:

=over 4

=item added

A scalar reference to the new value being set in replacement of the old one, so you can inspect it.

=item removed

A scalar reference to the old value being replaced, so you can inspect it.

=item type

The callback type: C<add>

=back

The C<remove> callback works the same way, but for any operation that tries to remove data from the string. It receives an hash reference with the following properties:

=over 4

=item removed

=item type

The callback type: C<remove>

=back

    my $str = Module::Generic::Scalar->new();
    $str->callback( remove => sub
    {
        my( $old, $new ) = @_;
        # Do some check to accept or reject
        return(1); # always return true to accept
    });
    $str->substr( 3, 4, $some_other_text ); # Attempt to change the string

This example above would call the callback passing it the old and new string.

To replace a callback, simply add another one. It will replace the previous one.

You can get the callback code associated with a callback by calling C<callback> with one argument: the callback type.

    my $code_ref = $str->callback( 'add' );

To remove a callback, pass C<undef>:

    $str->callback( add => undef );
    $str->callback( remove => undef );

By default, a scalar object is not tied to C<Module::Generic::Scalar::Tie> and no callback are set nor used. Be mindful when using callbacks since it will slow things down a little bit even though it is s very thin layer.

See L<perltie> for more information on tieing data.

=head3 THREAD SAFETY

The C<callback> method installs internal hooks that are triggered on scalar value mutation (e.g., when a new value is assigned). These callbacks are enabled by tying the scalar to an internal package (not part of the public API).

While this design is fully safe in single-threaded use, note the following if you are working in a multi-threaded environment:

=over 4

=item *

Callbacks may be triggered by assignments to the scalar value — e.g., C<$$scalar = "new value">.

=item *

If the scalar object is accessed by multiple threads, and a callback mutates shared state, this may lead to race conditions unless external locking is applied.

=item *

It is recommended that each scalar object be thread-local, or that the callback logic be thread-safe.

=back

The internal tied object is not exposed, and should not be accessed directly.

=head2 capitalise

Returns a new L<Module::Generic::Scalar> object representing the string with the words capitalised, done in a smart way. That is the special words like C<and>, C<but>, C<if>, C<on>, C<or>, C<the>, C<to>, etc. are not capitalised.

This is based on the work done by L<John Gruber and Aristotle Pagaltzis|https://gist.github.com/gruber/9f9e8650d68b13ce4d78>

=head2 chomp

Just like L<perlfunc/"chomp">, this removes the trailing new lines in the string object, if any.

As per the perl documentation for L<perlfunc/chomp>, "it returns the total number of characters removed from all its arguments."

=head2 chop

Just like L<perlfunc/"chop">, this removes the trailing character in the string object, no matter what it is, and returns the character chopped.

=head2 clone

Returns a copy of the object.

=head2 crypt

This takes a "salt" and returns an encrypted version of the string. See L<perlfunc/crypt>. Note that this does not work well on some BSD systems.

=head2 defined

Returns true or false whether the string object contains a defined string, i.e. not C<undef>

=head2 empty

Alias for L</reset>. This empties the scalar object, making it zero-byte in length.

=head2 error

Provided with an error message string, or an hash of parameters, and this will instantiate a new L<exception object|Module::Generic::Exception> and return C<undef>, or return an L<Module::Generic::Null> object in object context, such as when it is chained.

See L<Module::Generic/error> for more information.

=head2 fc

Just like L<perlfunc/"fc">, provided with a string, this enables comparison with casefolding.

To quote from the manual: "Casefolding is the process of mapping strings to a form where case differences are erased".

    lc($this) eq lc($that)    # Wrong!
    # or
    uc($this) eq uc($that)    # Also wrong!
    # or
    $this =~ /^\Q$that\E\z/i  # Right!
    # And now
    my $s = Module::Generic::Scalar( $this );
    $s->fc( $that );

=head2 hex

Returns the hex value of the string.

=head2 index

Given a sub string and an optional position, and this returns the position at which the sub string was found. as a L<Module::Generic::Number> object.

See L<perlfunc/"index">

=head2 is_alpha

Returns true if the string contains only alphabetic characters, or else it returns false.

This uses perl's C<[[:alpha:]]> to test.

=head2 is_alpha_numeric

Returns true if the string contains only alphabetic or numeric characters, or else it returns false.

This uses perl's C<[[:alnum:]]> to test.

=head2 is_empty

Returns true if the string is zero in length, or else it returns false.

=head2 is_lower

Returns true if the string contains only lower case characters, or else it returns false.

This uses perl's C<[[:lower:]]> to test.

=head2 is_numeric

Returns true if the string contains only numeric characters, or else it returns false.

This uses L<Scalar::Util/looks_like_number>

=head2 is_upper

Returns true if the string contains only upper case characters, or else it returns false.

This uses perl's C<[[:upper:]]> to test.

=head2 join

Takes a string as the delimiter and one or more elements and this will join the current string object and the other string elements provided using the string delimiter. For example:

    my $s = Module::Generic::Scalar->new( 'Jack John Paul Peter' );
    # Takes the string, split it by space (now an array), join it by comma (now a scalar) and rejoin it with more strings
    say $s->split( qr/[[:blank:]]+/ )->join( ', ' )->join( ', ', qw( Gabriel Raphael Emmanuel ) );
    # prints: Jack, John, Paul, Peter, Gabriel, Raphael, Emmanuel

Note that if, among the element passed to L</join>, an aray object is provided, it will not be expanded. Thus, if you do something like this:

    my $a = Module::Generic::Array->new( [qw( John Peter )] );
    say Module::Generic::Scalar->new( 'Paul' )->join( ', ', $a );

It wil lnot produce C<Paul, John, Peter>, but instead something like: C<Paul, Module::Generic::Array=ARRAY(0x5646f0116470)>

That's because there is no way to guess if the user wanted to pass on a list or just an object and making that kind of assumption is dangerous.

What you woud want to do instead is:

    say Module::Generic::Scalar->new( 'Paul' )->join( ', ', $a->list );

Or maybe

    say Module::Generic::Scalar->new( 'Paul' )->join( ', ', @$a );

Of course, passing an overloaded object that has the stringification capability will transform it automatically into a string before being joined. Thus:

    my $word1 = Module::Generic::Scalar->new( 'Hello' );
    my $word2 = Module::Generic::Scalar->new( 'world' );
    say $word1->join( ' ', $word2 );

will yield: C<Hello world> because L<Module::Generic::Scalar> objects have overloaded stringification capability.

It returns the newly formed string as a new L<Module::Generic::Scalar> object.

=head2 lc

Takes the current string object and returns a new L<Module::Generic::Scalar> object with the string all in lower case.

=head2 lcfirst

Takes the current string object and returns a new L<Module::Generic::Scalar> object with the first character of the string in lower case.

=head2 left

Provided with a number and this will get the chunk starting from the left of the string object.

    Module::Generic::Scalar->new( "Hello world" )->left( 5 );
    # will produce: Hello

See also L</right>

=head2 length

This returns the length of the string, as a L<Module::Generic::Number> object.

=head2 like

Provided with a string or a regular expression and this returns the value of the regular expression evaluation against the object string.

    my $s = "I disapprove of what you say, but I will defend to the death your right to say it";
    print( "Matches? ", $s->like( qr/\bapprove[[:blank:]\h]+what\b/ ) ? 'yes' : 'no', "\n" ); # print yes

If there is no match, it returns 0, but in object, list or scalar context or when there are matches, it returns a L<Module::Generic::RegexpCapture> object. This object stringifies into the number of matches, so you can do:

    if( $s->like( qr/[[:blank:]\h]+/ ) )
    {
        # Do something
    }

or

    if( my $rv = $s->like( qr/([[:blank:]\h]+)/ )->result )
    {
        my $first_match = $rv->capture->first;
        # Do something
    }

Regular expression can also be one from L<Regexp::Common>

See also L</match> and L</replace>

=head2 lower

Alias for L</lc>

=head2 ltrim

This removes any new line and space characters, i.e. C<\r> and C<\n> at the B<begining> of the string.

It takes an optional argument that can be an alternative string to remove at the end of the sstring or a regular expression, such as one provided with L</perlfunc/qr>

    $s->ltrim( qr/[[:blank:]\h]+/ ); # Remove all kind of leading whitespaces

It returns the object itself for chaining.

Regular expression can also be one from L<Regexp::Common>

See also L</rtrim>

=head2 match

Provided with a string or a regular expression like the one created with L<perlfunc/qr> and this returns true or false whether the string object matched or not.

    # $s is "Hello world"
    $s->match( 'world' ); # pass
    $s->match( qr/WORLD/i ); # pass
    $s->match( 'monde' ); # obviously fail

If there is no match, it returns 0, but in object, list or scalar context or when there are matches, it returns a L<Module::Generic::RegexpCapture> object. This object stringifies into the number of matches, so you can do:

    if( $s->match( qr/[[:blank:]\h]+/ ) )
    {
        # Do something
    }

or

    if( my $rv = $s->match( qr/([[:blank:]\h]+)/ )->result )
    {
        my $first_match = $rv->capture->first;
        # Do something
    }

Regular expression can also be one from L<Regexp::Common>

See also L</like> and L</replace>

=head2 object

Returns the current object. This is useful when chaining to force an object context. For example, with L</unpack>. In scalar context, L</unpack>, as per the L<perlfunc/unpack> documentation will return the first element:

    my $unpack = $unpack_data->unpack( "A10xA28xA8A*" );

But what if we wanted C<$unpack> to not be the first element, but the array object? For that, we would need to call L</unpack> in object context:

    my $unpack = $unpack_data->unpack( "A10xA28xA8A*" )->object;

=head2 open

Opens the scalar in read/write mode and returns an object with same capabilities as regular file handle.

=head2 ord

This returns the value of L<perlfunc/"ord"> on the string, as a L<Module::Generic::Number> object.

=head2 pack

Like L<perlfunc/pack>, this takes a template and convert the string object. "The resulting string is the concatenation of the converted values."

It returns a new L<Module::Generic::Scalar> object.

See also L<Module::Generic::Array/pack>

=head2 pad

Provided with a number n and a string and this will create n instance of the string. If the number is positive, the string will be placed at the begining and if negative, it will be placed at the end

    $s->pad( 3, 'X' );
    # XXXHello world

    $s->padd( -3, 'X' );
    # Hello worldXXX

=head2 pass_error

If provided with an error object, this will set it in this package and pass it along to the caller.

If no error object is provided, this will merely return C<undef>, or an L<Module::Generic::Null> object in object context, such as when it is chained.

See L<Module::Generic/pass_error> for more information.

=head2 pos

This sets or gets the position inside the string object. See L<perlfunc/pos> for detail about this.

=head2 prepend

Takes some data as its unique argument and prepend it to the scalar object underlying value.

If you want to pass multiple data, you will have to join them first:

    $s->prepend( join( '', qw( Cogito ergo sum ) ) );

=head2 quotemeta

Given a string, this return a new L<Module::Generic::Scalar> object with the given string characters escapeed with L<perlfunc/"quotemeta">.

=head2 replace

Provided with a string or a regular expression and a replacement string and this will replace all instance of the string or regular expression with the replacement string provided.

    # $s is Hello world
    my $rv = $s->replace( ' ', '_' ); # Hello_world
    my $rv = $s->replace( qr/[[:blank:]\h]+/, '_' ); # Hello_world

If there is no match, it returns 0, but in object, list or scalar context or when there are matches, it returns a L<Module::Generic::RegexpCapture> object. This object stringifies into the number of matches, so you can do:

    if( $s->replace( qr/[[:blank:]\h]+/, '_' ) )
    {
        # Do something
    }

or

    if( my $rv = $s->replace( qr/([[:blank:]\h]+)/, '_' )->result )
    {
        my $first_match = $rv->capture->first;
        # Do something
    }

Regular expression can also be one from L<Regexp::Common>

See also L</like> and L</match>

=head2 reset

This empty the string inside the object.

=head2 reverse

Given a string, this return a new L<Module::Generic::Scalar> object with the given string cin reverse order.

=head2 right

Provided with a number and this will get the chunk starting from the right of the string object.

    Module::Generic::Scalar->new( "Hello world" )->right( 5 );
    # will produce: world

See also L</left>

=head2 rindex

Given a sub string and an optional position, and this returns the position at which the sub string was found, starting from the end.

See L<perlfunc/"rindex">

=head2 rtrim

This removes any new line and space characters, i.e. C<\r> and C<\n> at the B<end> of the string.

It takes an optional argument that can be an alternative string to remove at the end of the sstring or a regular expression, such as one provided with L</perlfunc/qr>

    $s->rtrim( qr/[[:blank:]\h]+/ ); # Remove all kind of trailing whitespaces

It returns the object itself for chaining.

Regular expression can also be one from L<Regexp::Common>

See also L</ltrim>

=head2 scalar

Returns the string within this scalar object. This calls L</"as_string">

=head2 set

Provided with a scalar reference or scalar-based object like L<Module::Generic::Scalar> or an array reference and this sets the current string.

This acts the exact same way as for L</"new">, except it acts on the current object string.

=head2 split

Provided with a string or an expression and this returns the list in list context or, in scalar context, an array reference as an L<Module::Generic::Array> object.

Be careful that you cannot just do like perl's original split such as:

    my $a = $s->split( /\n/ );

Because C</\n/> is not passed as an argument, i.e. it results in no argument being passed, so you do need to either provide the expression as C<"\n"> or as a regular expression:

    my $a = $s->split( qr/\n/ );

It will warn you if no argument was provided.

Regular expression can also be one from L<Regexp::Common>

=head2 sprintf

Provided with a list of arguments, and this replace the placeholders just like L<perlfunc/"sprintf"> does.

=head2 substr

Provided with an offset, an optional length and an optional replacement string, and this modifies in-place the current object string and return a new L<Module::Generic::Scalar> object of the string segment thus altered or removed or replaced.

See L<perlfunc/"substr"> for more information.

    $s = 'I disapprove of what you say, but I will defend to the death your right to say it'; # Evelyn Hall, "Friends of Voltaire"
    $s->substr( 2, 13 );
    # returns a new object containing 'disapprove of'
    $s->substr( 2, 13, 'really do not approve' );
    # returns a new object containing 'disapprove of' and the current object now contains:
    # 'I really do not approve what you say, but I will defend to the death your right to say it'

=head2 tr

Provided with a search list and a replacement list and this will perform just like the perl core L<perlfunc/tr> function.

It also accepts options like C<cdsr> and returns the resulting value.

=head3 THREAD SAFETY AND SECURITY WARNING

The C<tr> method uses C<eval> to dynamically apply a transliteration pattern:

    $$self =~ tr/$search/$replace/$opts/;

This can be considered safe as long as the C<$search> and C<$replace> values come from a trusted source. However, if untrusted user input is passed to these arguments, it could result in a malformed or dangerous regular expression being compiled at runtime.

To ensure safety:

=over 4

=item *

Only use static or validated content in C<$search> and C<$replace>

=item *

Avoid using this method inside threads with shared objects unless the object is guarded externally

=back

=head2 trim

Provided with a target string or a regular expression, and this will remove any occurence of them in the string object.

=head2 uc

Takes the current string object and returns a new L<Module::Generic::Scalar> object with the string all in upper case.

=head2 ucfirst

Takes the current string object and returns a new L<Module::Generic::Scalar> object with the first character of the string in upper case.

=head2 undef

Sets the underlying string object to undef.

This would make

    print( $s->defined ? 'defined' : 'undefined', "\n" );

return false, but becareful that you cannot do:

    print( $s ? 'defined' : 'undefined', "\n" );

Because $s is the object so it would always return true.

If you stringify it like

    print( "$s" ? 'defined' : 'undefined', "\n" );

It would still return as defined, because this would be a defined string, albeit empty

=head2 unpack

    my $array = $s->unpack( "W" );
    # Returns the ord() value as a Module::Generic::Array object
    $s->unpack( "W" )->length;
    # Object context. Ditto
    my( $date, $desc, $income, $expend ) = $s->unpack( "A10xA27xA7A*" );
    # Returns a regular list of values

Does the reverse of L</pack>. Provided with a template, it takes the string object and expands it out into a list of values.

It returns an L<Module::Generic::Array> object in object or scalar context, and a regular list in list context.

=head2 upper

Alias for L</uc>

=head1 SERIALISATION

=for Pod::Coverage FREEZE

=for Pod::Coverage STORABLE_freeze

=for Pod::Coverage STORABLE_thaw

=for Pod::Coverage THAW

=for Pod::Coverage TO_JSON

Serialisation by L<CBOR|CBOR::XS>, L<Sereal> and L<Storable::Improved> (or the legacy L<Storable>) is supported by this package. To that effect, the following subroutines are implemented: C<FREEZE>, C<THAW>, C<STORABLE_freeze> and C<STORABLE_thaw>

=head1 THREAD-SAFETY

This module is designed to be fully thread-safe and process-safe, ensuring data integrity across Perl ithreads and mod_perl’s threaded Multi-Processing Modules (MPMs) such as Worker or Event. It relies on L<Module::Generic::Global> for thread-safe storage of shared state and employs synchronisation mechanisms to prevent data corruption and race conditions.

=head2 Synchronisation Mechanisms

=over 4

=item * B<Shared State>

The module uses L<Module::Generic::Global> to manage shared state in two namespaces:

=over 8

=item - C<errors>: Stores error objects for L</error> and L</pass_error>, keyed by object reference address, process ID, and thread ID (if applicable).

=item - C<loaded_classes>: Tracks loaded classes in C<_load_class>, keyed by class name and thread ID (if applicable).

=back

These repositories are C<:shared> when C<CAN_THREADS> is true (Perl supports ithreads), protected by L<perlfunc/lock> for Perl threads or L<APR::ThreadRWLock> for mod_perl threaded MPMs without L<threads>. In non-threaded environments, repositories are per-process, requiring no synchronisation.

=item * B<Tied Scalars>

When callbacks are set via C<callback>, the scalar is tied to C<Module::Generic::Scalar::Tie>. As of version 1.4.3, tied scalar operations (e.g., C<STORE>, C<FETCH>) are thread-safe, as they rely on L<Module::Generic::Global>’s synchronisation. However, callbacks themselves are not inherently thread-safe. If a callback accesses shared resources or performs complex operations, you must implement additional synchronisation (e.g., using L<perlfunc/lock> or L<Thread::Semaphore>).

=back

=head2 Context Key Isolation

L<Module::Generic::Global> uses context keys to isolate data:

=over 4

=item * B<Object-Level Keys>

Methods like L</error>, L</pass_error>, and C<DESTROY> use object-level keys (C<< <refaddr>;<pid>[;<tid>] >>), where C<refaddr> is the object’s reference address, C<pid> is the process ID, and C<tid> is the thread ID (if C<HAS_THREADS> is true). This ensures thread- and process-specific isolation for errors.

=item * B<Class-Level Keys>

C<_load_class> uses class-level keys (C<< <class>[;<tid>] >>), isolating loaded class tracking per thread or process.

=back

Keys differ between threaded (C<< <refaddr>;<pid>;<tid> >>) and non-threaded (C<< <refaddr>;<pid> >>) environments, preventing cross-context interference.

=head2 Error Handling

Errors are stored in the C<errors> namespace, with one error per object-process-thread combination. The package-global C<$ERROR> is retained for backward compatibility but is not cleared in C<DESTROY> to avoid affecting other objects. Errors are serialised using L<Storable::Improved> for thread compatibility.

=head2 mod_perl Considerations

=over 4

=item * B<Prefork MPM>

Data is per-process, requiring no additional synchronisation.

=item * B<Threaded MPMs (Worker/Event)>

Shared repositories are protected by L<Module::Generic::Global>’s synchronisation. Users should call C<Module::Generic::Global->cleanup_register> in mod_perl handlers to clear repositories after requests, preventing memory leaks.

=item * B<Thread-Unsafe Functions>

Avoid Perl functions like C<localtime>, C<readdir>, C<srand>, or operations like C<chdir>, C<umask>, C<chroot> in threaded MPMs, as they may affect all threads. Consult L<perlthrtut|http://perldoc.perl.org/perlthrtut.html> and L<mod_perl documentation|https://perl.apache.org/docs/2.0/user/coding/coding.html#Thread_environment_Issues> for details.

=back

=head2 Recommendations for Threaded Applications

=over 4

=item * Use non-tied scalars without additional synchronisation, as L<Module::Generic::Global> handles shared state.

=item * For tied scalars, ensure callbacks are thread-safe by adding locks for shared resources.

=item * In mod_perl, use C<cleanup_register> to manage memory.

=back

=head1 SEE ALSO

L<Module::Generic::Number>, L<Module::Generic::Array>, L<Module::Generic::Boolean>, L<Module::Generic::Hash>, L<Module::Generic::Dynamic>

=head1 AUTHOR

Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>

=head1 COPYRIGHT & LICENSE

Copyright (c) 2000-2024 DEGUEST Pte. Ltd.

You can use, copy, modify and redistribute this package and associated
files under the same terms as Perl itself.

=cut

