/perl/site/lib/Class/MakeMethods/Utility/Ref.pm

https://github.com/dwimperl/perl-5.12.3.0 · Perl · 171 lines · 103 code · 53 blank · 15 comment · 19 complexity · 3a6fa6af337343b6a7d41fc4347db06a MD5 · raw file

  1. =head1 NAME
  2. Class::MakeMethods::Utility::Ref - Deep copying and comparison
  3. =head1 SYNOPSIS
  4. use Class::MakeMethods::Utility::Ref qw( ref_clone ref_compare );
  5. $deep_copy = ref_clone( $original );
  6. $positive_zero_or_negative = ref_compare( $item_a, $item_b );
  7. =head1 DESCRIPTION
  8. This module provides utility functions to copy and compare arbitrary references, including full traversal of nested data structures.
  9. =cut
  10. ########################################################################
  11. package Class::MakeMethods::Utility::Ref;
  12. $VERSION = 1.000;
  13. @EXPORT_OK = qw( ref_clone ref_compare );
  14. sub import { require Exporter and goto &Exporter::import } # lazy Exporter
  15. use strict;
  16. ######################################################################
  17. =head2 REFERENCE
  18. The following functions are provided:
  19. =head2 ref_clone()
  20. Make a recursive copy of a reference.
  21. =cut
  22. use vars qw( %CopiedItems );
  23. # $deep_copy = ref_clone( $value_or_ref );
  24. sub ref_clone {
  25. local %CopiedItems = ();
  26. _clone( @_ );
  27. }
  28. # $copy = _clone( $value_or_ref );
  29. sub _clone {
  30. my $source = shift;
  31. my $ref_type = ref $source;
  32. return $source if (! $ref_type);
  33. return $CopiedItems{ $source } if ( exists $CopiedItems{ $source } );
  34. my $class_name;
  35. if ( "$source" =~ /^\Q$ref_type\E\=([A-Z]+)\(0x[0-9a-f]+\)$/ ) {
  36. $class_name = $ref_type;
  37. $ref_type = $1;
  38. }
  39. my $copy;
  40. if ($ref_type eq 'SCALAR') {
  41. $copy = \( $$source );
  42. } elsif ($ref_type eq 'REF') {
  43. $copy = \( _clone ($$source) );
  44. } elsif ($ref_type eq 'HASH') {
  45. $copy = { map { _clone ($_) } %$source };
  46. } elsif ($ref_type eq 'ARRAY') {
  47. $copy = [ map { _clone ($_) } @$source ];
  48. } else {
  49. $copy = $source;
  50. }
  51. bless $copy, $class_name if $class_name;
  52. $CopiedItems{ $source } = $copy;
  53. return $copy;
  54. }
  55. ######################################################################
  56. =head2 ref_compare()
  57. Attempt to recursively compare two references.
  58. If they are not the same, try to be consistent about returning a
  59. positive or negative number so that it can be used for sorting.
  60. The sort order is kinda arbitrary.
  61. =cut
  62. use vars qw( %ComparedItems );
  63. # $positive_zero_or_negative = ref_compare( $A, $B );
  64. sub ref_compare {
  65. local %ComparedItems = ();
  66. _compare( @_ );
  67. }
  68. # $positive_zero_or_negative = _compare( $A, $B );
  69. sub _compare {
  70. my($A, $B, $ignore_class) = @_;
  71. # If they're both simple scalars, use string comparison
  72. return $A cmp $B unless ( ref($A) or ref($B) );
  73. # If either one's not a reference, put that one first
  74. return 1 unless ( ref($A) );
  75. return - 1 unless ( ref($B) );
  76. # Check to see if we've got two references to the same structure
  77. return 0 if ("$A" eq "$B");
  78. # If we've already seen these items repeatedly, we may be running in circles
  79. return undef if ($ComparedItems{ $A } ++ > 2 and $ComparedItems{ $B } ++ > 2);
  80. # Check the ref values, which may be data types or class names
  81. my $ref_A = ref($A);
  82. my $ref_B = ref($B);
  83. return $ref_A cmp $ref_B if ( ! $ignore_class and $ref_A ne $ref_B );
  84. # Extract underlying data types
  85. my $type_A = ("$A" =~ /^\Q$ref_A\E\=([A-Z]+)\(0x[0-9a-f]+\)$/) ? $1 : $ref_A;
  86. my $type_B = ("$B" =~ /^\Q$ref_B\E\=([A-Z]+)\(0x[0-9a-f]+\)$/) ? $1 : $ref_B;
  87. return $type_A cmp $type_B if ( $type_A ne $type_B );
  88. if ($type_A eq 'HASH') {
  89. my @kA = sort keys %$A;
  90. my @kB = sort keys %$B;
  91. return ( $#kA <=> $#kB ) if ( $#kA != $#kB );
  92. foreach ( 0 .. $#kA ) {
  93. return ( _compare($kA[$_], $kB[$_]) or
  94. _compare($A->{$kA[$_]}, $B->{$kB[$_]}) or next );
  95. }
  96. return 0;
  97. } elsif ($type_A eq 'ARRAY') {
  98. return ( $#$A <=> $#$B ) if ( $#$A != $#$B );
  99. foreach ( 0 .. $#$A ) {
  100. return ( _compare($A->[$_], $B->[$_]) or next );
  101. }
  102. return 0;
  103. } elsif ($type_A eq 'SCALAR' or $type_A eq 'REF') {
  104. return _compare($$A, $$B);
  105. } else {
  106. return ("$A" cmp "$B")
  107. }
  108. }
  109. ########################################################################
  110. =head1 SEE ALSO
  111. See L<Class::MakeMethods> for general information about this distribution.
  112. See L<Ref> for the original version of the clone and compare functions used above.
  113. See L<Clone> (v0.09 on CPAN as of 2000-09-21) for a clone method with an XS implementation.
  114. The Perl6 RFP #67 proposes including clone functionality in the core.
  115. See L<Data::Compare> (v0.01 on CPAN as of 1999-04-24) for a Compare method which checks two references for similarity, but it does not provide positive/negative values for ordering purposes.
  116. =cut
  117. ######################################################################
  118. 1;