File Coverage

blib/lib/Data/YUID/Generator.pm
Criterion Covered Total %
statement 67 116 57.7
branch 1 20 5.0
condition 0 9 0.0
subroutine 23 31 74.1
pod 0 3 0.0
total 91 179 50.8


line stmt bran cond sub pod time code
1             # $Id$
2              
3             package Data::YUID::Generator;
4 2     2   10 use strict;
  2         4  
  2         64  
5 2     2   9 use warnings;
  2         4  
  2         57  
6 2     2   18 no warnings qw(deprecated); # for fields
  2         3  
  2         89  
7              
8 2     2   11 use vars qw{$VERSION};
  2         2  
  2         142  
9             $VERSION = "0.01";
10 2     2   9 use Carp;
  2         5  
  2         175  
11 2     2   12 use Config;
  2         10  
  2         99  
12              
13 2     2   2143 use fields qw(host_id start_time current_time min_id max_id ids make_id);
  2         3734  
  2         34  
14              
15 2     2   198 use constant EPOCH_OFFSET => 946684800; # Sat, Jan 1 2000 00:00 GMT
  2         4  
  2         154  
16              
17             ## | timestamp | serial | host |
18             ## | 36 bits | 12 | 16 |
19 2     2   10 use constant HOST_ID_BITS => 16;
  2         5  
  2         80  
20 2     2   17 use constant TIME_BITS => 36;
  2         3  
  2         123  
21 2     2   10 use constant SERIAL_BITS => 64 - HOST_ID_BITS - TIME_BITS;
  2         3  
  2         112  
22              
23 2     2   11 use constant HOST_SHIFT => HOST_ID_BITS;
  2         5  
  2         129  
24 2     2   11 use constant TIME_SHIFT => HOST_ID_BITS + SERIAL_BITS;
  2         4  
  2         111  
25 2     2   9 use constant SERIAL_SHIFT => HOST_ID_BITS;
  2         3  
  2         124  
26              
27 2     2   19 use constant SERIAL_INCREMENT => 1 << SERIAL_SHIFT;
  2         3  
  2         149  
28              
29 2     2   11 use constant HOST_ID_MAX => (1 << HOST_ID_BITS) - 1;
  2         3  
  2         134  
30 2     2   10 use constant TIME_MAX => (1 << TIME_BITS) - 1;
  2         2  
  2         123  
31 2     2   11 use constant TIME_MAX_SHIFTED => TIME_MAX << TIME_SHIFT;
  2         3  
  2         110  
32 2     2   10 use constant SERIAL_MAX => (1 << SERIAL_BITS) - 1;
  2         4  
  2         135  
33 2     2   10 use constant SERIAL_MAX_SHIFTED => SERIAL_MAX << SERIAL_SHIFT;
  2         3  
  2         107  
34              
35 2     2   11 use constant HOST_MAX => (1 << HOST_ID_BITS) - 1;
  2         9  
  2         105  
36             BEGIN {
37 2     2   30 use Config;
  2         4  
  2         152  
38 2 50   2   2191 unless ($Config{use64bitint}) {
39 0 0         eval "use Math::BigInt; 1;"
40             or croak "Please install Math::BigInt";
41             }
42             };
43              
44             sub new {
45 0     0 0   my Data::YUID::Generator $self = shift;
46 0 0         $self = fields::new( $self ) unless ref $self;
47              
48 0           my $host_id = shift;
49 0 0 0       if( !$host_id ) {
    0          
50 0           $host_id = int( rand( HOST_ID_MAX ) );
51             } elsif( $host_id < 0 || $host_id > HOST_ID_MAX ) {
52 0           warn __PACKAGE__ . ": host ID $host_id is not in range of [0," . HOST_ID_MAX . "]\n";
53 0           return undef;
54             }
55              
56 0           $self->{ host_id } = $host_id;
57 0           $self->{ start_time } = time;
58 0           $self->{ current_time } = 0;
59 0           $self->{ ids } = {};
60 0 0         if ($Config{use64bitint}) {
61 0     0     $self->{make_id} = sub { $self->_make_id_64bits(@_) };
  0            
62             }
63             else {
64 0     0     $self->{make_id} = sub { $self->_make_id_32bits(@_) };
  0            
65             }
66              
67 0           $self->_sync();
68              
69 0           return $self;
70             }
71              
72              
73             sub _sync {
74 0     0     my $self = shift;
75 0           my $key = shift;
76 0           my $time = time;
77 0 0         return if( $self->{ current_time } == $time ); # FIXME: check for clock skew
78 0           $self->{ current_time } = $time;
79 0           $self->{ min_id } = $self->{make_id}->( 0 );
80 0           $self->{ max_id } = $self->{make_id}->( SERIAL_MAX );
81 0 0         delete $self->{ ids }->{ $key } if $key; ## reset current timestamp
82             }
83              
84              
85             sub _make_id_64bits ($) {
86 0     0     my $self = shift;
87 0   0       my $serial = shift || 0;
88             return (($self->{ current_time } - EPOCH_OFFSET) << TIME_SHIFT) |
89 0           ($serial << SERIAL_SHIFT) | $self->{ host_id };
90             }
91              
92             sub _make_id_32bits ($) {
93 0     0     my $self = shift;
94 0   0       my $serial = shift || 0;
95 0           my $id = Math::BigInt->new($self->{ current_time } - EPOCH_OFFSET);
96             return $id->blsft(TIME_SHIFT)
97             ->bior(Math::BigInt->new($serial)->blsft(SERIAL_SHIFT))
98 0           ->bior($self->{ host_id });
99             }
100              
101             sub get_id ($) {
102 0     0 0   my $self = shift;
103 0   0       my $key = shift || "_";
104 0           $self->_sync($key);
105              
106 0 0         if( !exists $self->{ ids }->{ $key } ) {
107 0           $self->{ ids }->{ $key } = $self->{ min_id };
108 0           return $self->{ ids }->{ $key };
109             }
110              
111             return undef
112 0 0         if( $self->{ ids }->{ $key } >= $self->{ max_id } );
113              
114 0           $self->{ ids }->{ $key } += SERIAL_INCREMENT;
115 0           return $self->{ ids }->{ $key };
116             }
117              
118             ## deconstruct an id in its composing part, using id order:
119             ## (ts, serial, host)
120             sub decompose {
121 0     0 0   my $class = shift;
122 0           my $id = shift;
123 0           my $ts = $id >> TIME_SHIFT;
124 0           my $serial = ( $id >> HOST_SHIFT ) & SERIAL_MAX;
125 0           my $host = $id & HOST_MAX;
126 0           return ($ts, $serial, $host);
127             }
128              
129             1;