Add render() method to ZOOM::Exception.
[ZOOM-Perl-moved-to-github.git] / lib / ZOOM.pm
1 # $Id: ZOOM.pm,v 1.18 2005-11-16 14:49:30 mike Exp $
2
3 use strict;
4 use warnings;
5 use Net::Z3950::ZOOM;
6
7
8 package ZOOM;
9
10
11 # Member naming convention: hash-element names which begin with an
12 # underscore represent underlying ZOOM-C object descriptors; those
13 # which lack them represent Perl's ZOOM objects.  (The same convention
14 # is used in naming local variables where appropriate.)
15 #
16 # So, for example, the ZOOM::Connection class has an {_conn} element,
17 # which is a pointer to the ZOOM-C Connection object; but the
18 # ZOOM::ResultSet class has a {conn} element, which is a reference to
19 # the Perl-level Connection object by which it was created.  (It may
20 # be that we find we have no need for these references, but for now
21 # they are retained.)
22 #
23 # To get at the underlying ZOOM-C connection object of a result-set
24 # (if you ever needed to do such a thing, which you probably don't)
25 # you'd use $rs->{conn}->_conn().
26
27 # ----------------------------------------------------------------------------
28
29 # The "Error" package contains constants returned as error-codes.
30 package ZOOM::Error;
31 sub NONE { Net::Z3950::ZOOM::ERROR_NONE }
32 sub CONNECT { Net::Z3950::ZOOM::ERROR_CONNECT }
33 sub MEMORY { Net::Z3950::ZOOM::ERROR_MEMORY }
34 sub ENCODE { Net::Z3950::ZOOM::ERROR_ENCODE }
35 sub DECODE { Net::Z3950::ZOOM::ERROR_DECODE }
36 sub CONNECTION_LOST { Net::Z3950::ZOOM::ERROR_CONNECTION_LOST }
37 sub INIT { Net::Z3950::ZOOM::ERROR_INIT }
38 sub INTERNAL { Net::Z3950::ZOOM::ERROR_INTERNAL }
39 sub TIMEOUT { Net::Z3950::ZOOM::ERROR_TIMEOUT }
40 sub UNSUPPORTED_PROTOCOL { Net::Z3950::ZOOM::ERROR_UNSUPPORTED_PROTOCOL }
41 sub UNSUPPORTED_QUERY { Net::Z3950::ZOOM::ERROR_UNSUPPORTED_QUERY }
42 sub INVALID_QUERY { Net::Z3950::ZOOM::ERROR_INVALID_QUERY }
43 # The following are added specifically for this OO interface
44 sub CREATE_QUERY { 20001 }
45 sub QUERY_CQL { 20002 }
46 sub QUERY_PQF { 20003 }
47 sub SORTBY { 20004 }
48 sub CLONE { 20005 }
49 sub PACKAGE { 20006 }
50
51 # The "Event" package contains constants returned by last_event()
52 package ZOOM::Event;
53 sub NONE { Net::Z3950::ZOOM::EVENT_NONE }
54 sub CONNECT { Net::Z3950::ZOOM::EVENT_CONNECT }
55 sub SEND_DATA { Net::Z3950::ZOOM::EVENT_SEND_DATA }
56 sub RECV_DATA { Net::Z3950::ZOOM::EVENT_RECV_DATA }
57 sub TIMEOUT { Net::Z3950::ZOOM::EVENT_TIMEOUT }
58 sub UNKNOWN { Net::Z3950::ZOOM::EVENT_UNKNOWN }
59 sub SEND_APDU { Net::Z3950::ZOOM::EVENT_SEND_APDU }
60 sub RECV_APDU { Net::Z3950::ZOOM::EVENT_RECV_APDU }
61 sub RECV_RECORD { Net::Z3950::ZOOM::EVENT_RECV_RECORD }
62 sub RECV_SEARCH { Net::Z3950::ZOOM::EVENT_RECV_SEARCH }
63
64 # ----------------------------------------------------------------------------
65
66 package ZOOM;
67
68 sub diag_str {
69     my($code) = @_;
70
71     # Special cases for error specific to the OO layer
72     if ($code == ZOOM::Error::CREATE_QUERY) {
73         return "can't create query object";
74     } elsif ($code == ZOOM::Error::QUERY_CQL) {
75         return "can't set CQL query";
76     } elsif ($code == ZOOM::Error::QUERY_PQF) {
77         return "can't set prefix query";
78     } elsif ($code == ZOOM::Error::SORTBY) {
79         return "can't set sort-specification";
80     } elsif ($code == ZOOM::Error::CLONE) {
81         return "can't clone record";
82     } elsif ($code == ZOOM::Error::PACKAGE) {
83         return "can't create package";
84     }
85
86     return Net::Z3950::ZOOM::diag_str($code);
87 }
88
89 ### More of the ZOOM::Exception instantiations should use this
90 sub _oops {
91     my($code, $addinfo) = @_;
92
93     die new ZOOM::Exception($code, diag_str($code), $addinfo);
94 }
95
96 # ----------------------------------------------------------------------------
97
98 package ZOOM::Exception;
99
100 sub new {
101     my $class = shift();
102     my($code, $message, $addinfo) = @_;
103     ### support diag-set, too
104
105     return bless {
106         code => $code,
107         message => $message,
108         addinfo => $addinfo,
109     }, $class;
110 }
111
112 sub code {
113     my $this = shift();
114     return $this->{code};
115 }
116
117 sub message {
118     my $this = shift();
119     return $this->{message};
120 }
121
122 sub addinfo {
123     my $this = shift();
124     return $this->{addinfo};
125 }
126
127 sub render {
128     my $this = shift();
129     my $res = "ZOOM error " . $this->code() . ' "' . $this->message() . '"';
130     $res .= ' (addinfo: "' . $this->addinfo() . '")' if $this->addinfo();
131     return $res;
132 }
133
134 # This means that untrapped exceptions render nicely.
135 use overload '""' => \&render;
136
137 # ----------------------------------------------------------------------------
138
139 package ZOOM::Options;
140
141 sub new {
142     my $class = shift();
143     my($p1, $p2) = @_;
144
145     my $opts;
146     if (@_ == 0) {
147         $opts = Net::Z3950::ZOOM::options_create();
148     } elsif (@_ == 1) {
149         $opts = Net::Z3950::ZOOM::options_create_with_parent($p1->_opts());
150     } elsif (@_ == 2) {
151         $opts = Net::Z3950::ZOOM::options_create_with_parent2($p1->_opts(),
152                                                               $p2->_opts());
153     } else {
154         die "can't make $class object with more than 2 parents";
155     }
156
157     return bless {
158         _opts => $opts,
159     }, $class;
160 }
161
162 # PRIVATE to this class and ZOOM::Connection::create() and
163 # ZOOM::Connection::package()
164 #
165 sub _opts {
166     my $this = shift();
167
168     my $_opts = $this->{_opts};
169     die "{_opts} undefined: has this Options block been destroy()ed?"
170         if !defined $_opts;
171
172     return $_opts;
173 }
174
175 sub option {
176     my $this = shift();
177     my($key, $value) = @_;
178
179     my $oldval = Net::Z3950::ZOOM::options_get($this->_opts(), $key);
180     Net::Z3950::ZOOM::options_set($this->_opts(), $key, $value)
181         if defined $value;
182
183     return $oldval;
184 }
185
186 sub option_binary {
187     my $this = shift();
188     my($key, $value) = @_;
189
190     my $dummylen = 0;
191     my $oldval = Net::Z3950::ZOOM::options_getl($this->_opts(),
192                                                 $key, $dummylen);
193     Net::Z3950::ZOOM::options_setl($this->_opts(), $key,
194                                    $value, length($value))
195         if defined $value;
196
197     return $oldval;
198 }
199
200 # This is a bit stupid, since the scalar values that Perl returns from
201 # option() can be used as a boolean; but it's just possible that some
202 # applications will rely on ZOOM_options_get_bool()'s idiosyncratic
203 # interpretation of what constitutes truth.
204 #
205 sub bool {
206     my $this = shift();
207     my($key, $default) = @_;
208
209     return Net::Z3950::ZOOM::options_get_bool($this->_opts(), $key, $default);
210 }
211
212 # .. and the next two are even more stupid
213 sub int {
214     my $this = shift();
215     my($key, $default) = @_;
216
217     return Net::Z3950::ZOOM::options_get_int($this->_opts(), $key, $default);
218 }
219
220 sub set_int {
221     my $this = shift();
222     my($key, $value) = @_;
223
224     Net::Z3950::ZOOM::options_set_int($this->_opts(), $key, $value);
225 }
226
227 #   ### Feel guilty.  Feel very, very guilty.  I've not been able to
228 #       get the callback memory-management right in "ZOOM.xs", with
229 #       the result that the values of $function and $udata passed into
230 #       this function, which are on the stack, have sometimes been
231 #       freed by the time they're used by __ZOOM_option_callback(),
232 #       with hilarious results.  To avoid this, I copy the values into
233 #       module-scoped globals, and pass _those_ into the extension
234 #       function.  To avoid overwriting those globals by subsequent
235 #       calls, I keep all the old ones, pushed onto the @_function and
236 #       @_udata arrays, which means that THIS FUNCTION LEAKS MEMORY
237 #       LIKE IT'S GOING OUT OF FASHION.  Not nice.  One day, I should
238 #       fix this, but for now there's more important fish to fry.
239 #
240 my(@_function, @_udata);
241 sub set_callback {
242     my $o1 = shift();
243     my($function, $udata) = @_;
244
245     push @_function, $function;
246     push @_udata, $udata;
247     Net::Z3950::ZOOM::options_set_callback($o1->_opts(),
248                                            $_function[-1], $_udata[-1]);
249 }
250
251 sub destroy {
252     my $this = shift();
253
254     Net::Z3950::ZOOM::options_destroy($this->_opts());
255     $this->{_opts} = undef;
256 }
257
258
259 # ----------------------------------------------------------------------------
260
261 package ZOOM::Connection;
262
263 sub new {
264     my $class = shift();
265     my($host, $port) = @_;
266
267     my $_conn = Net::Z3950::ZOOM::connection_new($host, $port || 0);
268     my($errcode, $errmsg, $addinfo) = (undef, "dummy", "dummy");
269     $errcode = Net::Z3950::ZOOM::connection_error($_conn, $errmsg, $addinfo);
270     die new ZOOM::Exception($errcode, $errmsg, $addinfo) if $errcode;
271
272     return bless {
273         host => $host,
274         port => $port,
275         _conn => $_conn,
276     };
277 }
278
279 sub create {
280     my $class = shift();
281     my($options) = @_;
282
283     my $_conn = Net::Z3950::ZOOM::connection_create($options->_opts());
284     return bless {
285         host => undef,
286         port => undef,
287         _conn => $_conn,
288     };
289 }
290
291 # PRIVATE to this class
292 sub _conn {
293     my $this = shift();
294
295     my $_conn = $this->{_conn};
296     die "{_conn} undefined: has this Connection been destroy()ed?"
297         if !defined $_conn;
298
299     return $_conn;
300 }
301
302 sub error_x {
303     my $this = shift();
304
305     my($errcode, $errmsg, $addinfo, $diagset) = (undef, "dummy", "dummy", "d");
306     $errcode = Net::Z3950::ZOOM::connection_error_x($this->_conn(), $errmsg,
307                                                     $addinfo, $diagset);
308     return ($errcode, $errmsg, $addinfo, $diagset);
309 }
310
311 sub errcode {
312     my $this = shift();
313     return Net::Z3950::ZOOM::connection_errcode($this->_conn());
314 }
315
316 sub errmsg {
317     my $this = shift();
318     return Net::Z3950::ZOOM::connection_errmsg($this->_conn());
319 }
320
321 sub addinfo {
322     my $this = shift();
323     return Net::Z3950::ZOOM::connection_addinfo($this->_conn());
324 }
325
326 sub connect {
327     my $this = shift();
328     my($host, $port) = @_;
329
330     Net::Z3950::ZOOM::connection_connect($this->_conn(), $host, $port);
331     my($errcode, $errmsg, $addinfo) = (undef, "dummy", "dummy");
332     $errcode = Net::Z3950::ZOOM::connection_error($this->_conn(),
333                                                   $errmsg, $addinfo);
334     die new ZOOM::Exception($errcode, $errmsg, $addinfo) if $errcode;
335     # No return value
336 }
337
338 sub option {
339     my $this = shift();
340     my($key, $value) = @_;
341
342     my $oldval = Net::Z3950::ZOOM::connection_option_get($this->_conn(), $key);
343     Net::Z3950::ZOOM::connection_option_set($this->_conn(), $key, $value)
344         if defined $value;
345
346     return $oldval;
347 }
348
349 sub option_binary {
350     my $this = shift();
351     my($key, $value) = @_;
352
353     my $dummylen = 0;
354     my $oldval = Net::Z3950::ZOOM::connection_option_getl($this->_conn(),
355                                                           $key, $dummylen);
356     Net::Z3950::ZOOM::connection_option_setl($this->_conn(), $key,
357                                              $value, length($value))
358         if defined $value;
359
360     return $oldval;
361 }
362
363 sub search {
364     my $this = shift();
365     my($query) = @_;
366
367     my $_rs = Net::Z3950::ZOOM::connection_search($this->_conn(),
368                                                   $query->_query());
369     my($errcode, $errmsg, $addinfo) = (undef, "dummy", "dummy");
370     $errcode = Net::Z3950::ZOOM::connection_error($this->_conn(),
371                                                   $errmsg, $addinfo);
372     die new ZOOM::Exception($errcode, $errmsg, $addinfo) if $errcode;
373
374     return _new ZOOM::ResultSet($this, $query, $_rs);
375 }
376
377 sub search_pqf {
378     my $this = shift();
379     my($pqf) = @_;
380
381     my $_rs = Net::Z3950::ZOOM::connection_search_pqf($this->_conn(), $pqf);
382     my($errcode, $errmsg, $addinfo) = (undef, "dummy", "dummy");
383     $errcode = Net::Z3950::ZOOM::connection_error($this->_conn(),
384                                                   $errmsg, $addinfo);
385     die new ZOOM::Exception($errcode, $errmsg, $addinfo) if $errcode;
386
387     return _new ZOOM::ResultSet($this, $pqf, $_rs);
388 }
389
390 sub scan {
391     my $this = shift();
392     my($startterm) = @_;
393
394     my $_ss = Net::Z3950::ZOOM::connection_scan($this->_conn(), $startterm);
395     my($errcode, $errmsg, $addinfo) = (undef, "dummy", "dummy");
396     $errcode = Net::Z3950::ZOOM::connection_error($this->_conn(),
397                                                   $errmsg, $addinfo);
398     die new ZOOM::Exception($errcode, $errmsg, $addinfo) if $errcode;
399
400     return _new ZOOM::ScanSet($this, $startterm, $_ss);
401 }
402
403 sub package {
404     my $this = shift();
405     my($options) = @_;
406
407     my $_o = defined $options ? $options->_opts() :
408         Net::Z3950::ZOOM::options_create();
409     my $_p = Net::Z3950::ZOOM::connection_package($this->_conn(), $_o)
410         or ZOOM::_oops(ZOOM::Error::PACKAGE);
411
412     return _new ZOOM::Package($this, $options, $_p);
413 }
414
415 sub destroy {
416     my $this = shift();
417
418     Net::Z3950::ZOOM::connection_destroy($this->_conn());
419     $this->{_conn} = undef;
420 }
421
422
423 # ----------------------------------------------------------------------------
424
425 package ZOOM::Query;
426
427 sub new {
428     my $class = shift();
429     die "You can't create $class objects: it's a virtual base class";
430 }
431
432 # PRIVATE to this class and ZOOM::Connection::search()
433 sub _query {
434     my $this = shift();
435
436     my $_query = $this->{_query};
437     die "{_query} undefined: has this Query been destroy()ed?"
438         if !defined $_query;
439
440     return $_query;
441 }
442
443 sub sortby {
444     my $this = shift();
445     my($sortby) = @_;
446
447     Net::Z3950::ZOOM::query_sortby($this->_query(), $sortby) == 0
448         or ZOOM::_oops(ZOOM::Error::SORTBY, $sortby);
449 }
450
451 sub destroy {
452     my $this = shift();
453
454     Net::Z3950::ZOOM::query_destroy($this->_query());
455     $this->{_query} = undef;
456 }
457
458
459 package ZOOM::Query::CQL;
460 our @ISA = qw(ZOOM::Query);
461
462 sub new {
463     my $class = shift();
464     my($string) = @_;
465
466     my $q = Net::Z3950::ZOOM::query_create()
467         or ZOOM::_oops(ZOOM::Error::CREATE_QUERY);
468     Net::Z3950::ZOOM::query_cql($q, $string) == 0
469         or ZOOM::_oops(ZOOM::Error::QUERY_CQL, $string);
470
471     return bless {
472         _query => $q,
473     }, $class;
474 }
475
476
477 package ZOOM::Query::PQF;
478 our @ISA = qw(ZOOM::Query);
479
480 sub new {
481     my $class = shift();
482     my($string) = @_;
483
484     my $q = Net::Z3950::ZOOM::query_create()
485         or ZOOM::_oops(ZOOM::Error::CREATE_QUERY);
486     Net::Z3950::ZOOM::query_prefix($q, $string) == 0
487         or ZOOM::_oops(ZOOM::Error::QUERY_PQF, $string);
488
489     return bless {
490         _query => $q,
491     }, $class;
492 }
493
494
495 # ----------------------------------------------------------------------------
496
497 package ZOOM::ResultSet;
498
499 sub new {
500     my $class = shift();
501     die "You can't create $class objects directly";
502 }
503
504 # PRIVATE to ZOOM::Connection::search() and ZOOM::Connection::search_pqf()
505 sub _new {
506     my $class = shift();
507     my($conn, $query, $_rs) = @_;
508
509     return bless {
510         conn => $conn,
511         query => $query,        # This is not currently used, which is
512                                 # just as well since it could be
513                                 # either a string (when the RS is
514                                 # created with search_pqf()) or a
515                                 # ZOOM::Query object (when it's
516                                 # created with search())
517         _rs => $_rs,
518     }, $class;
519 }
520
521 # PRIVATE to this class
522 sub _rs {
523     my $this = shift();
524
525     my $_rs = $this->{_rs};
526     die "{_rs} undefined: has this ResultSet been destroy()ed?"
527         if !defined $_rs;
528
529     return $_rs;
530 }
531
532 sub option {
533     my $this = shift();
534     my($key, $value) = @_;
535
536     my $oldval = Net::Z3950::ZOOM::resultset_option_get($this->_rs(), $key);
537     Net::Z3950::ZOOM::resultset_option_set($this->_rs(), $key, $value)
538         if defined $value;
539
540     return $oldval;
541 }
542
543 sub size {
544     my $this = shift();
545
546     return Net::Z3950::ZOOM::resultset_size($this->_rs());
547 }
548
549 sub record {
550     my $this = shift();
551     my($which) = @_;
552
553     my $_rec = Net::Z3950::ZOOM::resultset_record($this->_rs(), $which);
554     ### Check for error -- but how?
555     return undef if !defined $_rec;
556
557     # For some reason, I have to use the explicit "->" syntax in order
558     # to invoke the ZOOM::Record constructor here, even though I don't
559     # have to do the same for _new ZOOM::ResultSet above.  Weird.
560     return ZOOM::Record->_new($this, $which, $_rec);
561 }
562
563 sub record_immediate {
564     my $this = shift();
565     my($which) = @_;
566
567     my $_rec = Net::Z3950::ZOOM::resultset_record_immediate($this->_rs(),
568                                                             $which);
569     ### Check for error -- but how?
570     return undef if !defined $_rec;
571
572     return ZOOM::Record->_new($this, $which, $_rec);
573 }
574
575 sub cache_reset {
576     my $this = shift();
577
578     Net::Z3950::ZOOM::resultset_cache_reset($this->_rs());
579 }
580
581 sub records {
582     my $this = shift();
583     my($start, $count, $return_records) = @_;
584
585     my $raw = Net::Z3950::ZOOM::resultset_records($this->_rs(), $start, $count,
586                                                   $return_records);
587     ### Why don't we throw an exception if $raw is undefined?
588     return undef if !defined $raw;
589
590     # We need to package up the returned records in ZOOM::Record objects
591     my @res = ();
592     for my $i (0 .. @$raw-1) {
593         my $_rec = $raw->[$i];
594         if (!defined $_rec) {
595             push @res, undef;
596         } else {
597             push @res, ZOOM::Record->_new($this, $start+$i, $_rec);
598         }
599     }
600
601     return \@res;
602 }
603
604 sub sort {
605     my $this = shift();
606     my($sort_type, $sort_spec) = @_;
607
608     return Net::Z3950::ZOOM::resultset_sort1($this->_rs(),
609                                              $sort_type, $sort_spec);
610 }
611
612 sub destroy {
613     my $this = shift();
614
615     Net::Z3950::ZOOM::resultset_destroy($this->_rs());
616     $this->{_rs} = undef;
617 }
618
619
620 # ----------------------------------------------------------------------------
621
622 package ZOOM::Record;
623
624 sub new {
625     my $class = shift();
626     die "You can't create $class objects directly";
627 }
628
629 # PRIVATE to ZOOM::ResultSet::record(),
630 # ZOOM::ResultSet::record_immediate(), ZOOM::ResultSet::records() and
631 # ZOOM::Record::clone()
632 #
633 sub _new {
634     my $class = shift();
635     my($rs, $which, $_rec) = @_;
636
637     return bless {
638         rs => $rs,
639         which => $which,
640         _rec => $_rec,
641     }, $class;
642 }
643
644 # PRIVATE to this class
645 sub _rec {
646     my $this = shift();
647
648     my $_rec = $this->{_rec};
649     die "{_rec} undefined: has this Record been destroy()ed?"
650         if !defined $_rec;
651
652     return $_rec;
653 }
654
655 sub render {
656     my $this = shift();
657
658     my $len = 0;
659     my $string = Net::Z3950::ZOOM::record_get($this->_rec(), "render", $len);
660     # I don't think we need '$len' at all.  ### Probably the Perl-to-C
661     # glue code should use the value of `len' as well as the opaque
662     # data-pointer returned, to ensure that the SV contains all of the
663     # returned data and does not stop at the first NUL character in
664     # binary data.  Carefully check the ZOOM_record_get() documentation.
665     return $string;
666 }
667
668 sub raw {
669     my $this = shift();
670
671     my $len = 0;
672     my $string = Net::Z3950::ZOOM::record_get($this->_rec(), "raw", $len);
673     # See comment about $len in render()
674     return $string;
675 }
676
677 sub clone {
678     my $this = shift();
679
680     my $raw = Net::Z3950::ZOOM::record_clone($this->_rec())
681         or ZOOM::_oops(ZOOM::Error::CLONE);
682
683     # Arg 1 (rs) is undefined as the new record doesn't belong to an RS
684     return _new ZOOM::Record(undef, undef, $raw);
685 }
686
687 sub destroy {
688     my $this = shift();
689
690     Net::Z3950::ZOOM::record_destroy($this->_rec());
691     $this->{_rec} = undef;
692 }
693
694
695 # ----------------------------------------------------------------------------
696
697 package ZOOM::ScanSet;
698
699 sub new {
700     my $class = shift();
701     die "You can't create $class objects directly";
702 }
703
704 # PRIVATE to ZOOM::Connection::scan(),
705 sub _new {
706     my $class = shift();
707     my($conn, $startterm, $_ss) = @_;
708
709     return bless {
710         conn => $conn,
711         startterm => $startterm,
712         _ss => $_ss,
713     }, $class;
714 }
715
716 # PRIVATE to this class
717 sub _ss {
718     my $this = shift();
719
720     my $_ss = $this->{_ss};
721     die "{_ss} undefined: has this ScanSet been destroy()ed?"
722         if !defined $_ss;
723
724     return $_ss;
725 }
726
727 sub option {
728     my $this = shift();
729     my($key, $value) = @_;
730
731     my $oldval = Net::Z3950::ZOOM::scanset_option_get($this->_ss(), $key);
732     Net::Z3950::ZOOM::scanset_option_set($this->_ss(), $key, $value)
733         if defined $value;
734
735     return $oldval;
736 }
737
738 sub size {
739     my $this = shift();
740
741     return Net::Z3950::ZOOM::scanset_size($this->_ss());
742 }
743
744 sub term {
745     my $this = shift();
746     my($which) = @_;
747
748     my($occ, $len) = (0, 0);
749     my $term = Net::Z3950::ZOOM::scanset_term($this->_ss(), $which,
750                                               $occ, $len);
751     ### Throw exception?
752     return undef if !defined $term;
753     die "length of term '$term' differs from returned len=$len"
754         if length($term) != $len;
755
756     return ($term, $occ);
757 }
758
759 sub display_term {
760     my $this = shift();
761     my($which) = @_;
762
763     my($occ, $len) = (0, 0);
764     my $term = Net::Z3950::ZOOM::scanset_display_term($this->_ss(), $which,
765                                                       $occ, $len);
766     ### Throw exception?
767     return undef if !defined $term;
768     die "length of display term '$term' differs from returned len=$len"
769         if length($term) != $len;
770
771     return ($term, $occ);
772 }
773
774 sub destroy {
775     my $this = shift();
776
777     Net::Z3950::ZOOM::scanset_destroy($this->_ss());
778     $this->{_ss} = undef;
779 }
780
781
782 # ----------------------------------------------------------------------------
783
784 package ZOOM::Package;
785
786 sub new {
787     my $class = shift();
788     die "You can't create $class objects directly";
789 }
790
791 # PRIVATE to ZOOM::Connection::package(),
792 sub _new {
793     my $class = shift();
794     my($conn, $options, $_p) = @_;
795
796     return bless {
797         conn => $conn,
798         options => $options,
799         _p => $_p,
800     }, $class;
801 }
802
803 # PRIVATE to this class
804 sub _p {
805     my $this = shift();
806
807     my $_p = $this->{_p};
808     die "{_p} undefined: has this Package been destroy()ed?"
809         if !defined $_p;
810
811     return $_p;
812 }
813
814 sub option {
815     my $this = shift();
816     my($key, $value) = @_;
817
818     my $oldval = Net::Z3950::ZOOM::package_option_get($this->_p(), $key);
819     Net::Z3950::ZOOM::package_option_set($this->_p(), $key, $value)
820         if defined $value;
821
822     return $oldval;
823 }
824
825 sub send {
826     my $this = shift();
827     my($type) = @_;
828
829     Net::Z3950::ZOOM::package_send($this->_p(), $type);
830     my($errcode, $errmsg, $addinfo) = (undef, "dummy", "dummy");
831     $errcode = Net::Z3950::ZOOM::connection_error($this->{conn}->_conn(),
832                                                   $errmsg, $addinfo);
833     die new ZOOM::Exception($errcode, $errmsg, $addinfo) if $errcode;
834 }
835
836 sub destroy {
837     my $this = shift();
838
839     Net::Z3950::ZOOM::package_destroy($this->_p());
840     $this->{_p} = undef;
841 }
842
843
844 1;