Bivio::Type::EnumSet
# Copyright (c) 1999-2007 bivio Software, Inc. All rights reserved.
# $Id$
package Bivio::Type::EnumSet;
use strict;
use Bivio::Base 'Bivio::Type';
my(%_INFO) = ();
sub clear {
my($vector, $bits) = _parse_args(\@_);
# Clears I<bit>(s) in I<vector>. Returns I<vector> as a string_ref (always).
foreach my $bit (@$bits) {
vec($$vector, $bit->as_int, 1) = 0;
}
return $vector;
}
sub compare_defined {
my($proto) = shift;
my($left, $right) = map((_parse_args([$proto, $_]))[0], @_);
my($bits) = [$proto->get_enum_type->get_list];
return $$left eq $$right ? 0
: grep($proto->is_set(\$left, $_), @$bits)
<=> grep($proto->is_set(\$right, $_), @$bits);
}
sub from_array {
my($proto, $enums) = @_;
# Returns set from an array of enum values (numbers, names, or enums).
my($t) = $proto->get_enum_type;
return $proto->set($proto->get_min, map($t->from_any($_), @$enums));
}
sub from_literal {
my($proto, $value) = @_;
$proto->internal_from_literal_warning
unless wantarray;
return $proto->from_sql_column($value);
}
sub from_sql_column {
my($proto, $value) = @_;
# Returns the bit vector for the database value (a hex string)
return undef unless defined($value);
# Just in case there is blank padding
$value =~ s/\s/0/g;
return pack('h*', $value);
}
sub get_empty {
my($proto) = @_;
# Return an empty EnumSet.
my($enum) = $proto->get_enum_type;
my($length) = $enum->get_max->as_int + 1;
my($vector) = '';
foreach my $i (0..$length) {
vec($vector, $i, 1) = 0;
}
return \$vector;
}
sub get_enum_type {
# Returns the enumerated type this set uses.
b_die('abstract method');
}
sub get_max {
my($proto) = @_;
# Returns the bit vector with all the bits set to one.
my($class) = ref($proto) || $proto;
return $_INFO{$class}->{max};
}
sub get_min {
my($proto) = @_;
# Returns the bit vector with all the bits set to zero.
my($class) = ref($proto) || $proto;
return $_INFO{$class}->{min};
}
sub get_width {
# Must return width of database CHAR field.
b_die('abstract method');
}
sub initialize {
my($proto) = @_;
# Initializes state for the particular enum.
my($class) = ref($proto) || $proto;
return if $_INFO{$class};
my($enum) = $proto->get_enum_type;
b_die($enum, ': not an enum')
unless UNIVERSAL::isa($enum, 'Bivio::Type::Enum');
b_die($enum, ": can't be an EnumSet, because can be negative")
if $enum->can_be_negative;
my($length) = $enum->get_max->as_int + 1;
my($min) = '';
vec($min, $length - 1, 1) = 0;
my($max) = '';
foreach my $i (0..$length-1) {
vec($max, $i, 1) = 1;
}
$_INFO{$class} = {
min => $min,
max => $max,
};
# Pad appropriately, because the enum may be smaller than the enumset.
my($width) = 2 * $proto->get_width;
b_die($enum, ": EnumSet ($width) is narrower than Enum (",
length($max), ")",
) if $width - length($max) < 0;
foreach my $m ('min', 'max') {
my($s) = unpack('h*', $_INFO{$class}->{$m});
$s .= '0' x ($width - length($s));
$_INFO{$class}->{$m} = pack('h*', $s);
}
return;
}
sub is_set {
my($vector, $bits) = _parse_args(\@_);
# Returns true if all I<bit>(s) are set in I<vector>.
foreach my $bit (@$bits) {
return 0 unless vec($$vector, $bit->as_int, 1);
}
return 1;
}
sub set {
my($vector, $bits) = _parse_args(\@_);
# Sets I<bit>(s) in I<vector>. Returns I<vector> as string_ref (always).
foreach my $bit (@$bits) {
vec($$vector, $bit->as_int, 1) = 1;
}
return $vector;
}
sub to_array {
my($proto) = @_;
# Converts to an array of enums.
my($v) = _parse_args(\@_);
return [map(
$proto->is_set($v, $_) ? $_ : (),
$proto->get_enum_type->get_list,
)];
}
sub to_literal {
my(undef, $value) = @_;
# Same as L<to_sql_param|"to_sql_param">.
return shift->SUPER::to_literal(@_)
unless defined($value);
return shift->to_sql_param(@_);
}
sub to_sql_list {
my($vector) = _parse_args(\@_);
# Returns a list of the form '(N,M,O,P)'.
return '()' unless ref($vector) && length($$vector);
return '('.join(',',
map {vec($$vector, $_, 1) ? ($_) : ()} 0..length($$vector)*8-1)
.')';
}
sub to_sql_param {
my($proto, $value) = @_;
# Returns the database representation (lower nybble first, hex string)
# of the bit vector.
# Note: Two characters are required _per_ byte.
return undef unless defined($value) && length($value);
b_die('value must be non ref') if ref($value);
my($res) = unpack('h*', $value);
my($width) = 2 * $proto->get_width;
# Exact match means field is correct.
return $res if length($res) == $width;
b_die('field too long') if length($res) > $width;
# Pad with zeroes
$res .= '0' x ($width - length($res));
return $res;
}
sub _parse_args {
my($args) = @_;
# Returns ($vector, $bits) based on @$args.
#
# Could technically typecheck @$bits.
my($proto) = shift(@$args);
my($t);
my($vector) = shift(@$args);
return (
(ref($vector) ? $vector : \$vector),
[map(ref($_) ? $_ : ($t ||= $proto->get_enum_type)->from_any($_),
map(ref($_) eq 'ARRAY' ? @$_ : $_,
@$args))],
);
}
1;