File Coverage

blib/lib/RDF/SIO/Utils.pm
Criterion Covered Total %
statement 8 10 80.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 12 14 85.7


line stmt bran cond sub pod time code
1             package RDF::SIO::Utils;
2             {
3             $RDF::SIO::Utils::VERSION = '0.003';
4             }
5             BEGIN {
6 1     1   48446 $RDF::SIO::Utils::VERSION = '0.003';
7             }
8 1     1   10 use strict;
  1         1  
  1         34  
9 1     1   5 use Carp;
  1         2  
  1         56  
10 1     1   496 use RDF::SIO::Utils::Trine;
  0            
  0            
11             use RDF::SIO::Utils::Constants;
12              
13             use vars qw($AUTOLOAD @ISA);
14              
15             use vars qw /$VERSION/;
16              
17              
18             my $xsd = "http://www.w3.org/2001/XMLSchema#";
19              
20              
21             =head1 NAME
22              
23             RDF::SIO::Utils - tools for working with the SIO ontology
24              
25             =head1 SYNOPSIS
26              
27             use RDF::SIO::Utils;
28              
29             my $SIO = RDF::SIO::Utils->new();
30            
31             # auto created RDF::SIO::Utils::Trine is there
32             my $m = $SIO->Trine->temporary_model();
33            
34             # create a subject to execute annotation and parsing on
35             my $s = $SIO->Trine->iri('http://mydata.com/patient101');
36              
37             # we want to add a blood pressure attribute to this patient, using
38             # the "hasBloodPressure" predicate from our own ontology
39             # SIO::Utils will automatically add the SIO:has_attribute
40             # predicate as a second connection to this node
41             my $BloodPressure = $SIO->addAttribute(model => $m,
42             node => $s,
43             predicate => "http://myontology.org/pred/hasBloodPressure",
44             attributeType => "http://myontology.org/class/BloodPressure",
45             );
46              
47              
48             #this is how to handle the success/failure of most calls in this module
49             if ($BloodPressure){
50             print "Blood Pressure attribute ID is ",$BloodPressure->as_string,"\n";
51             } else {
52             print $SIO->error_message(), "\n";
53             die; # or do something useful...
54             }
55              
56             my $Measurement = $SIO->addMeasurement(model => $m,
57             node => $BloodPressure,
58             attributeID => "http://mydatastore.org/observation1",
59             value => "115",
60             valueType => "^^int",
61             unit => "mmHg"
62             );
63              
64             print "Measurement node ID is ",$Measurement->as_string,"\n";
65              
66             print "extracting info from this Measurement:\n";
67             my ($value, $unit) = $SIO->getUnitValue(model => $m, node => $Measurement);
68             print "Measurement was - Value: $value\nUnit: $unit\n\n";
69            
70              
71             my $types = $SIO->getAttributeTypes(
72             model => $m,
73             node => $s);
74             print "\n\nAll Attribute Types:\n ";
75             print join "\n", @$types, "\n";
76              
77              
78             my $bp = $SIO->getAttributesByType(
79             model =>$m,
80             node => $s,
81             attributeType =>"http://myontology.org/class/BloodPressure", );
82             print "Nodes of type 'Blood Pressure':\n";
83             print join "\n", @$bp, "\n";
84              
85             print "\nand the piece de resistance...
86             traverse the whole shebang in one call!\n";
87            
88             my $data = $SIO->getAttributeMeasurements(
89             model => $m,
90             node => $s,
91             attributeType => "http://myontology.org/class/BloodPressure"
92             );
93              
94             foreach my $data_point(@$data){
95             my ($value, $unit) = ("", "");
96             ($value, $unit) = @$data_point;
97             print "found Blood Pressure data point: $value, $unit\n";
98             }
99              
100             # to dump out the entire model as RDF
101             use RDF::Trine::Serializer::RDFXML;
102             my $serializer = RDF::Trine::Serializer::RDFXML->new( );
103             print $serializer->serialize_model_to_string($m);
104             print "\n\n";
105              
106              
107             =cut
108              
109             =head1 DESCRIPTION
110              
111             The Semantic Science Integrated Ontology (SIO) is an upper-ontology
112             designed specifically to represent scientific data. This module helps
113             users create data compliant with this ontology, and parse that data
114              
115              
116             =head1 AUTHORS
117              
118             Mark Wilkinson (markw at illuminae dot com)
119              
120             =cut
121              
122             =head1 METHODS
123              
124              
125             =head2 new
126              
127             Usage : my $SIO = SIO::Utils->new();
128             Function : Create a helper module for the SIO ontology
129             Returns : a helper module for the SIO ontology
130             Args : none
131              
132              
133             =cut
134              
135             {
136              
137             # Encapsulated:
138             # DATA
139             #___________________________________________________________
140             #ATTRIBUTES
141             my %_attr_data = # DEFAULT ACCESSIBILITY
142             (
143             error_message => [ undef, 'read/write' ],
144             Trine => [ undef, 'read/write' ],
145              
146             );
147              
148             #_____________________________________________________________
149             # METHODS, to operate on encapsulated class data
150             # Is a specified object attribute accessible in a given mode
151             sub _accessible {
152             my ( $self, $attr, $mode ) = @_;
153             $_attr_data{$attr}[1] =~ /$mode/;
154             }
155              
156             # Classwide default value for a specified object attribute
157             sub _default_for {
158             my ( $self, $attr ) = @_;
159             $_attr_data{$attr}[0];
160             }
161              
162             # List of names of all specified object attributes
163             sub _standard_keys {
164             keys %_attr_data;
165             }
166              
167              
168             }
169              
170              
171             sub new {
172             my ( $caller, %args ) = @_;
173             my $caller_is_obj = ref( $caller );
174             return $caller if $caller_is_obj;
175             my $class = $caller_is_obj || $caller;
176             my $proxy;
177             my $self = bless {}, $class;
178             foreach my $attrname ( $self->_standard_keys ) {
179             if ( exists $args{$attrname} ) {
180             $self->{$attrname} = $args{$attrname};
181             } elsif ( $caller_is_obj ) {
182             $self->{$attrname} = $caller->{$attrname};
183             } else {
184             $self->{$attrname} = $self->_default_for( $attrname );
185             }
186             }
187             my $Trine = RDF::SIO::Utils::Trine->new();
188             $self->Trine($Trine);
189              
190             return $self;
191             }
192              
193              
194             =head2 addAttribute
195              
196             Usage : $SIO->addAttribute(%args);
197             Function : add a new attribute to an SIO entity
198             Returns : 0 on failure (check $SIO->error_message after failure for
199             details. Returns the attribute's Trine node on success.
200             Args : model - an RDF::Trine::Model to hold results
201             node - the RDF::Trine::Node to which we will attach the
202             attribute. This is the "subject" of the triples,
203             and (duh) should be part of the model above.
204             predicate - optional, your desired predicate URI.
205             Defaults to sio:hasAttribute if not provided.
206             attributeID - optional,the URI of your attribute,
207             defaults to a bnode, but MUST be typed
208             attributeType - The rdf:type of your attribute as a URI
209             value - optional, the value (e.g. 17 or "hi")
210             valueType - optional, and this varies depending on
211             what "value:" is. If it is a string, then
212             enter the language here using "lang:xx" (e.g.
213             valueType = 'lang:en'). If it is a non-string,
214             then enter its xsd:type in turtle syntax
215             (e.g. valueType='^^int')
216             unit - optional, URI to a Unit Ontology type, or a string
217             context - optional, URI of the context (for n-quads and named graphs)
218             Description: Creates the following structure:
219            
220             node
221             has_attribute
222             attributeID/_bnode
223             [has_value value]
224             [has_unit unit]
225             [rdf_type type] (dflt SIO:attribute)
226              
227              
228             =cut
229              
230             sub addAttribute {
231             my ($self, %args) = @_;
232              
233             my $model = $args{model};
234            
235             my $subject = $args{node}; # subject
236             my $attr_uri = $args{attributeID}; # object -> will become a _bnode if not specified
237             my $val = $args{value}; # some numerical or string value
238             my $unit = $args{unit}; # the unit of measure if numerical value
239             my $attribute_type = $args{attributeType}; # specification of what "type" of object we have
240             my $value_type = $args{valueType}; # specification of what "type" of value we have string, non-string, or OWL class
241             my $pred = $args{predicate}; # what is the predicate connecting this attribute to the subject node?
242             my $context = $args{context};
243              
244            
245             my $attribute; # the URI of the attribute node as a Trine
246             my $predicate; # the URI of the predicate, as a Trine
247             my $value; # literal value as a trine
248             my $classType; # the rdf:type of the attributeID ($attribute_type) as a Trine
249              
250             # create a trine spewer
251             my $T = $self->Trine;
252              
253             # deal with context first
254             if ($context && !($context=~ /^http:/)){
255             $self->error_message("your context, if provided, must be a URI.");
256             return 0;
257             }
258             if ($context){
259             $context = $T->iri($context);
260             }
261              
262            
263             # deal with the attribute node - need everything as a Trine before we start building he model
264            
265             if ($attr_uri){ # if they give us a URI, it should be a URI and they should give us a type
266             unless ($attr_uri =~ /^http:/){
267             $self->error_message("Your attributeID isn't a URI... it should be");
268             return 0;
269             }
270             unless ($attribute_type && ($attribute_type =~ /^http:/)){
271             $self->error_message("The attribute type for $attr_uri isn't specified, or isn't a URI. That's not very polite! Please don't give me a URI and not tell me what kind of 'thing' it represents!");
272             return 0;
273             }
274             $attribute = $T->iri($attr_uri); # make the URI into a trine
275             $classType = $T->iri($attribute_type); # create an rdf:type for it - whatever they said
276            
277             } else { # if they don't give us a URI (bnode), they must still give us a type
278             if (!$attribute_type || !($attribute_type =~ /^http:/) ){ # if there's a value, but not an attribute type or the attribute type isn't a URI, then fail
279             $self->error_message("Not enough information provided to create the statements. You must tell me the type of attribute (attributeType)");
280             return 0;
281             }
282            
283             $attribute = $T->blank(); # create a bnode for the attribute
284             $classType = $T->iri($attribute_type); # and set classtype to whatever they said
285            
286             }
287            
288              
289             if ($pred && !($pred=~ /^http:/)){
290             $self->error_message("your predicate, if provided, must be a URI.");
291             return 0;
292             }
293             if ($pred){
294             $predicate = $T->iri($pred);
295             } else {
296             $predicate = $T->iri(SIO_HAS_ATTR);
297             }
298            
299            
300              
301             # deal with the unit node - need everything as a Trine before we start building the model
302             if ($unit && !$val){
303             $self->error_message("Ummmm... a unit but no value? I dont' know what to do with that");
304             return 0;
305            
306             }
307             if ($unit && ($unit =~ /http:/)) { # the unit is a reference to some unit ontology, apparently
308             $unit = $T->iri($unit); # dont' ask questions, just make it a URI Trine
309            
310             } elsif ($unit) { # then I guess it's a literal??
311             $unit = $T->literal($unit); # don't know what kind of literal, so pass only one arg
312             }
313              
314              
315             # deal with the value node - need everything as a Trine before we start building the model
316            
317             if ($value_type && ($value_type =~ /^\^\^(\S+)/)){ # it's a non-string literal (e.g. ^^int)
318             $value = $T->literal($val, "", $xsd.$1);
319            
320             } elsif ($value_type && ($value_type =~ /^lang:(\S+)/)) { # it's a string literal, language provided (eg. lang:en)
321             $value = $T->literal($val, $1, "");
322            
323             } elsif ($value_type) { # it's a string literal, language provided (eg. lang:en)
324             $self->error_message("you provided a value type that isn't recognized. Either a language (e.g. lang:en) or an xsd type (e.g. ^^int) are the only options allowed.");
325             return 0;
326            
327             } elsif ($val) { # then I guess it's a literal??
328             $value = $T->literal($val); # don't know what kind of literal, so pass only one arg
329             } else {
330             $value = ""; # this will be caught and ignored by the updateModel routine
331             }
332            
333              
334              
335             &_updateModel($T, $model, $subject, $predicate, $attribute, $classType, $value, $unit, $context );
336            
337             return $attribute;
338            
339             }
340              
341              
342             =head2 addMeasurement
343              
344             Usage : $SIO->addMeasurement(%args);
345             Function : A specialized type of addAttribute - rdf types and
346             predicates are auto-selected to be compliant with SIO
347             Returns : 0 on failure (check $SIO->error_message after failure for
348             details.) Returns the Measurement's Trine node on success.
349             Args : model - an RDF::Trine::Model to hold results
350             node - the RDF::Trine::Node to which we will attach the
351             attribute. This is the "subject" of the triples,
352             and (duh) should be part of the model above.
353             attributeID - optional,the URI of your attribute,
354             defaults to a bnode.
355             value - required, the value of the measurement. In SIO, all
356             measurements are **numeric**
357             valueType - required, values xsd:type in turtle syntax
358             (e.g. valueType='^^int')
359             unit - optional, URI to a Unit Ontology type, or a string
360             context - optional, URI of the named graph for n-quads
361             Description: Creates the following structure:
362            
363             node
364             has_measurement_value
365             sio:measurement_value(attributeID or bnode)
366             has_value value
367             [has_unit unit]
368              
369              
370             =cut
371              
372              
373             sub addMeasurement {
374             my ($self, %args) = @_;
375            
376             my $model = $args{model};
377            
378             my $subject = $args{node}; # subject
379             my $attr_uri = $args{attributeID}; # object -> will become a _bnode if not specified
380             my $val = $args{value}; # some numerical or string value
381             my $unit = $args{unit}; # the unit of measure if numerical value
382             my $attribute_type = SIO_MEASUREMENT_VALUE; # specification of what "type" of object we have
383             my $value_type = $args{valueType}; # specification of what "type" of value we have string, non-string, or OWL class
384             my $pred = SIO_HAS_MEASUREMENT_VALUE; # what is the predicate connecting this attribute to the subject node?
385             my $context = $args{context};
386              
387             unless ($value_type =~ /^\^\^/){
388             $self->error_message("SIO only allows ^^int, ^^float, or ^^double as the types of values for measurements.");
389             return 0;
390            
391             }
392            
393             unless ($val) {
394             $self->error_message("Measurements must have values...");
395             return 0;
396              
397             }
398             my $return = $self->addAttribute(
399             model => $model,
400             node => $subject,
401             attributeID => $attr_uri,
402             value => $val,
403             unit => $unit,
404             attributeType => $attribute_type,
405             valueType => $value_type,
406             predicate => $pred,
407             context => $context,
408             );
409            
410             return $return;
411            
412             }
413              
414              
415              
416             =head2 getAttributeTypes
417              
418             Usage : $SIO->getAttributeTypes(%args);
419             Function : Retrieve a list of attribute types for a given node
420             Returns : listref of matching RDF::Trine::Nodes,
421             or listref of [0] on error (see $SIO->error_message)
422             Args : model - an RDF::Trine::Model with the graph of interest
423             node - the RDF::Trine::Node to query attributes of
424             as_string - if set to 'true', the routine will return a
425             listref of string URIs, instead of Trine nodes.
426            
427             All arguments are required.
428              
429              
430             =cut
431              
432              
433              
434             sub getAttributeTypes {
435             my ($self, %args) = @_;
436             my $model = $args{model};
437             my $subject = $args{node}; # subject
438             my $asString = $args{as_string};
439            
440             my @all_types;
441            
442             my $hasAttr = $self->Trine->iri(SIO_HAS_ATTR);
443             my $rdfType = $self->Trine->iri(RDF_TYPE);
444            
445             my $iterator = $model->get_statements ($subject, $hasAttr, undef);
446             my @statements = $iterator->get_all;
447             foreach my $statement(@statements){
448             my $obj = $statement->object;
449             my $it = $model->get_statements($obj, $rdfType, undef);
450             my @types = $it->get_all;
451             if ($asString){
452             push @all_types, map {$_->object->as_string} @types;
453             } else {
454             push @all_types, map {$_->object} @types;
455             }
456            
457             }
458              
459             return \@all_types;
460            
461             }
462              
463              
464              
465             =head2 getAttributesByType
466              
467             Usage : $SIO->getAttributesByType(%args);
468             Function : You specify the rdf:type of the attribute you want
469             and this routine retrieves all such SIO:attribute nodes
470             from the node you submit.
471             Returns : listref of matching RDF::Trine::Nodes,
472             or listref of [0] on error (see $SIO->error_message)
473             Args : model - an RDF::Trine::Model with the graph of interest
474             node - the RDF::Trine::Node to query attributes of
475             attributeType - the URI of your attribute-type of interest
476            
477             All arguments are required.
478              
479              
480             =cut
481              
482              
483             sub getAttributesByType {
484             my ($self, %args) = @_;
485             my $model = $args{model};
486             my $subject = $args{node}; # subject
487             my $attribute_type = $args{attributeType}; # specification of what "type" of object we have
488              
489             unless ($model && $subject && $attribute_type) {
490             $self->error_message("all arguments - model, nodem, and attributeType - are required");
491             return [0];
492              
493             }
494              
495             my $attributeType = $self->Trine->iri($attribute_type); # make type a Trine
496             my $hasAttr = $self->Trine->iri(SIO_HAS_ATTR);
497             my $rdfType = $self->Trine->iri(RDF_TYPE);
498            
499             my $iterator = $model->get_statements ($subject, $hasAttr, undef);
500             my @statements = $iterator->get_all;
501              
502             my @matching_attributes;
503             foreach my $statement(@statements){
504             my $obj = $statement->object;
505             my $it = $model->get_statements($obj, $rdfType, $attributeType);
506             my @types = $it->get_all;
507             if ($types[0]){push @matching_attributes, $obj }
508            
509             }
510             return \@matching_attributes;
511             }
512              
513              
514              
515              
516             =head2 getUnitValue
517              
518             Usage : $SIO->getUnitValue(%args);
519             Function : You provide an SIO attribute node and this routine
520             retrieves the value and unit of that node (if available)
521             Returns : list containing (Value, Unit) as scalars (note that if Unit is
522             a reference to an ontology node, it will return the URI
523             of that node, NOT the node as a Trine). If there is no unit,
524             undef will be returned as the second value of the list.
525             Returns an empty list on failure.
526             Args : model - an RDF::Trine::Model with the graph of interest
527             node - the RDF::Trine::Node to query value/unit of
528            
529             Description: The routine assumes that your graph has the following
530             SIO-compliant structure:
531            
532             SIO:attribute
533             --has_value--> Value(literal)
534             --has_unit---> Unit (literal or URI node)
535            
536             Note that the routine assumes (as per my understanding of
537             SIO) that only one value and one unit are allowed
538             for any given attribute, so even if there is more than
539             one, only one value/unit will returned!
540              
541              
542             =cut
543              
544              
545             sub getUnitValue {
546             my ($self, %args) = @_;
547             my $model = $args{model};
548             my $subject = $args{node}; # subject
549             my $hasUnit = $self->Trine->iri(SIO_HAS_UNIT);
550             my $hasValue = $self->Trine->iri(SIO_HAS_VALUE);
551              
552             unless ($model && $subject) {
553             $self->error_message("all arguments - model, node - are required");
554             return ();
555              
556             }
557              
558             my $iterator = $model->get_statements ($subject, $hasValue, undef);
559             my @statements = $iterator->get_all;
560             my $statement = shift @statements;
561             unless ($statement) {
562             $self->error_message("no value node was found");
563             return ();
564              
565             }
566             my $value = $statement->object->value;
567              
568              
569             $iterator = $model->get_statements ($subject, $hasUnit, undef);
570             @statements = $iterator->get_all;
571             $statement = shift @statements;
572             my $unit;
573             if ($statement) {
574             $unit = $statement->object->value;
575             }
576             return ($value, $unit);
577             }
578              
579              
580             =head2 getAttributeMeasurements
581              
582             Usage : $SIO->getAttributeMeasurements(%args);
583             Function : a short-cut to retrieve SIO-style attribute measurements.
584             (see Description for expected graph structure)
585             retrieves value/unit pairs for each measurement of an attribute
586             Returns : nested listref [[value, unit], [value, unit],...]
587             for the precise details of the inner listrefs,
588             see 'getUnitValue' documentation
589             Args : model - an RDF::Trine::Model with the graph of interest
590             node - the RDF::Trine::Node to query attributes of
591             (would be SIO:thing in the diagram below)
592             attributeType - URI of type of attribute you want the
593             measurement values for
594             (e.g. http://myontology.org/SomeAttributeType)
595            
596             Description: The routine assumes that your graph has the following
597             SIO-compliant structure:
598            
599             SIO:thing
600             --has_attribute--> SIO:attribute
601             --rdf:type--> your:SomeAttributeType
602             --has_measurement_value--> SIO:measurement
603             --has_value--> Value(literal)
604             --has_unit---> Unit (literal or URI node)
605            
606             Note that the routine assumes (as per my understanding of
607             SIO) that only one value and one unit are allowed
608             for any given SIO:measurement, so even if there is more than
609             one, only one value/unit will returned!
610            
611             this subroutine is ~equivalent to:
612             getAttributesByType(SomeAttributeType)
613             getAttributesByType(SIO:measurement)
614             getUnitValue()
615              
616              
617             =cut
618              
619              
620              
621             sub getAttributeMeasurements{
622             my ($self, %args) = @_;
623             my $model = $args{model};
624             my $subject = $args{node}; # subject
625             my $attributeType = $args{attributeType};
626              
627             my @response;
628            
629             my $attributes = $self->getAttributesByType(
630             model => $model,
631             node => $subject,
632             attributeType => $attributeType
633             );
634             my $hasMeasurementValue = $self->Trine->iri(SIO_HAS_MEASUREMENT_VALUE);
635            
636             foreach my $attribute(@$attributes){
637             my $iterator = $model->get_statements ($attribute, $hasMeasurementValue, undef);
638             my @statements = $iterator->get_all;
639             foreach my $statement(@statements){
640             my $measurement = $statement->object;
641             my ($value, $unit) = $self->getUnitValue(node => $measurement, model => $model,);
642             push @response, [$value, $unit];
643             }
644            
645             }
646             return \@response;
647             }
648              
649              
650             sub _addStatementsToModel {
651             my ($model, $statements) = @_;
652             map {$model->add_statement($_)} @{$statements};
653             }
654              
655             sub _updateModel{
656             my ($T, $model, $subject, $predicate, $attribute, $classType, $value, $unit, $context ) = @_;
657             my $rdfType = $T->iri(RDF_TYPE);
658             my $SIO_ATTR = $T->iri(SIO_ATTRIBUTE);
659             my $hasUnit = $T->iri(SIO_HAS_UNIT);
660             my $hasValue = $T->iri(SIO_HAS_VALUE);
661             my $hasAttr = $T->iri(SIO_HAS_ATTR); # don't know if we should put this on also... for those without reasoners??
662              
663             my @statements;
664              
665             if ($context){
666             push @statements, RDF::Trine::Statement::Quad->new($subject, $predicate, $attribute, $context);
667             } else {
668             push @statements, $T->statement($subject, $predicate, $attribute);
669             }
670            
671             unless ( $predicate->equal($hasAttr) ){
672             if ($context){
673             push @statements, RDF::Trine::Statement::Quad->new($subject, $hasAttr, $attribute, $context);
674             } else {
675             push @statements, $T->statement($subject, $hasAttr, $attribute);
676             }
677             }
678             if ($context){
679             push @statements, RDF::Trine::Statement::Quad->new($attribute, $rdfType, $classType, $context);
680             push @statements, RDF::Trine::Statement::Quad->new($attribute, $rdfType, $SIO_ATTR, $context);
681             } else {
682             push @statements, $T->statement($attribute, $rdfType, $classType);
683             push @statements, $T->statement($attribute, $rdfType, $SIO_ATTR);
684             }
685              
686             if ($value){
687             if ($context){
688             push @statements, RDF::Trine::Statement::Quad->new($attribute, $hasValue, $value, $context);
689             } else {
690             push @statements, $T->statement($attribute, $hasValue, $value);
691             }
692             }
693            
694             if ($unit){
695             if ($context){
696             push @statements, RDF::Trine::Statement::Quad->new($attribute, $hasUnit, $unit, $context);
697             }else {
698             push @statements, $T->statement($attribute, $hasUnit, $unit);
699             }
700             }
701              
702             &_addStatementsToModel($model, \@statements);
703              
704             }
705              
706              
707             sub AUTOLOAD {
708              
709             no strict "refs";
710             my ( $self, $newval ) = @_;
711             $AUTOLOAD =~ /.*::(\w+)/;
712             my $attr = $1;
713             if ( $self->_accessible( $attr, 'write' ) ) {
714             *{$AUTOLOAD} = sub {
715             if ( defined $_[1] ) { $_[0]->{$attr} = $_[1]; }
716             return $_[0]->{$attr};
717             }; ### end of created subroutine
718             ### this is called first time only
719             if ( defined $newval ) {
720             $self->{$attr} = $newval;
721             }
722             return $self->{$attr};
723             } elsif ( $self->_accessible( $attr, 'read' ) ) {
724             *{$AUTOLOAD} = sub {
725             return $_[0]->{$attr};
726             }; ### end of created subroutine
727             return $self->{$attr};
728             }
729            
730             # Must have been a mistake then...
731             croak "No such method: $AUTOLOAD";
732             }
733             sub DESTROY { }
734             1;