#!/usr/bin/perl -w # ################################################################################ # Location: ............. eBay/API/XML/Tools/CodeGen/XSD # File: ................. Annotation.pm # Original Author: ...... Milenko Milanovic # Last Modified By: ..... Robert Bradley / Jeff Nokes # Last Modified: ........ 03/30/2007 @ 23:08 # ################################################################################ =pod =head1 eBay::API::XML::Tools::CodeGen::XSD::Annotation Generate annotations/documentation for the classes. =cut package eBay::API::XML::Tools::CodeGen::XSD::Annotation; # Global Variables our $VERSION = '0.01'; # The version of this module. use strict; use warnings; use Exporter; use Data::Dumper; sub new { my $classname = shift; my $rhXmlSimple = shift; my $self = {}; bless($self, $classname); #print "11.rhAnnotation=|" . Dumper($rhXmlSimple) . "\n"; # 1. set documentation property my $sDocumentation = $rhXmlSimple->{'xs:documentation'}; # Sometimes XML::Simple returns a hash for documentation: # Example: VerifyAddItemRequestType ( property: 'ExternalProductID' # In such cases create sDocumentation by concatenating # values from the hash. # if ( defined $sDocumentation && ref($sDocumentation) eq 'HASH' ) { my @values = values %$sDocumentation; if ( (scalar @values ) == 0 ) { $sDocumentation = ''; } else { @values = sort { length($a) <=> length($b) } @values; $sDocumentation = join("\n", @values); } } $self->setDocumentation( $sDocumentation ); # 2. find all calls and attributes related to them # sets: # $self->setCallsInfo( ); an array of CallInfo-s # $self->setAdditionalAppInfo (); a string $self->_initAppInfo( $rhXmlSimple->{'xs:appinfo'} ); return $self; } sub _initAppInfo { my $self = shift; my $refAppInfo = shift; my $rArrObjCallInfo = []; my $sAdditionalAppInfo = ''; ############################################## if ( defined $refAppInfo ) { my $rArrHashAppInfo = $self->getAnnotationAppInfoAsArrayOfCallInfos( $refAppInfo ); my $addAppInfo = ''; foreach my $rhAppInfo ( @$rArrHashAppInfo ) { my $refCallInfo = $rhAppInfo->{'CallInfo'}; # # retrieve additional app info for current property # This is really strange: # I found a few more elements in rhAppInfo hash table: # SiteInfo, Constraint, APIInfo # They are extramly rarely used. I will concatenate them into one # generic variable: 'additionalAppInfo' # delete $rhAppInfo->{'CallInfo'}; $sAdditionalAppInfo .= createAddAppInfo ( $rhAppInfo, $sAdditionalAppInfo ); # Sometimes 'refCallInfo' contain an array of elements # and sometimes it contains just one element # 'CallInfo' => { # 'CallName' => 'GetMyeBayBuying', # 'RequiredInput' => 'No', # 'Context' => 'WatchList' # } # So if a hash ref is returned, added it to an array. if ( ! defined $refCallInfo ) { next; } my $raCallInfo = hashMakeSureItsArray ( $refCallInfo, 'WARNING: refCallInfo IS NEITHER ref to a hash NOR a ref to an array!?' ); foreach my $rhCallInfo ( @$raCallInfo ) { # pass a hash, rather then a hash ref my $pCallInfo = eBay::API::XML::Tools::CodeGen::XSD::Annotation::CallInfo->new ( %$rhCallInfo ); push @$rArrObjCallInfo, $pCallInfo; } } } ############################################## $self->setAdditionalAppInfo($sAdditionalAppInfo); $self->setCallsInfo( $rArrObjCallInfo ); } =head2 createAddAppInfo() Some times there are a few more elements in rhAppInfo hash table: SiteInfo, Constraint, APIInfo They are rarely used. Concatenate them into one generic variable: 'additionalAppInfo' =cut sub createAddAppInfo { my $rhAppInfo = shift; my $sResult = shift; my @keys = keys %$rhAppInfo; if ( (scalar @keys) > 0 ) { foreach my $key ( @keys ) { $sResult .= "$key: "; my $value = $rhAppInfo->{$key}; if ( ref ($value) eq 'ARRAY' ) { my $cnt = 0; foreach my $elem ( @$value ) { if ( $cnt > 0 ) { $sResult .= ", "; } if ( ref($elem) eq 'HASH' ) { $sResult = createAddAppInfo ( $elem, $sResult ); } else { $sResult .= $elem; } $cnt++; } $sResult .= "\n"; } elsif ( ref ($value) eq 'HASH' ) { $sResult = createAddAppInfo ( $value, $sResult ); } else { $sResult .= $value . "\n"; } } } return $sResult; } sub getAnnotationAppInfoAsArrayOfCallInfos { my $self = shift; my $refAppInfo = shift; # reference to a structure containing property's # AppInfo. The structure can be either # a ref to a hash or a ref to an array # # In a very few cases, xs:appinfo can be an array # Actually I found only one such case: # CategoryType.NumOfItems # This means that we have to assume that array is always returned # # All except one case have 'xs:appinfo' as a ref to a hash # convert the following into an array # $VAR1 = { # 'CallInfo' => { # 'CallName' => 'AddDispute', # 'RequiredInput' => 'Yes' # } # }; # This is already an array, nothing to do!! # The only property that has 'xs:appinfo' as array is: # CategoryType.NumOfItems # $VAR1 = [ # { # 'CallInfo' => { # 'Returned' => 'Conditionally', # 'CallName' => [ # 'GetCategoryListings', # 'GetSearchResults' # ], # 'Context' => 'CategoryArray' # } # }, # { # 'CallInfo' => { # 'Returned' => 'Conditionally', # 'CallName' => 'GetCategoryListings', # 'Context' => 'CategoryArray' # } # } # ]; my $rArrHashCallInfo = hashMakeSureItsArray ( $refAppInfo, 'WARNING: refAppInfo IS NEITHER ref to a hash NOR a ref to an array!?'); return $rArrHashCallInfo; } =head2 hashMakeSureItsArray() Arguments: 1. [R] Either a hash ref or an array ref 2. [0] Warning message if previous parameter is not a reference. Returns: an array ref Description: If there are multiple elements with a same name, XML:Simple return them as a reference to an array, otherwise it returns a value. The problem is that for some XML document, the element appears only once in the structure, while for other documents, more than once. Here, we make sure that such elements are returned in a array even in cases when there is only one element. Obviously we do this only for elements that we know can be returned as an array. =cut sub hashMakeSureItsArray { my $ref = shift; my $notArefMessage = shift; my $ra; if ( ref ( $ref ) eq 'HASH' ) { $ra = [ $ref ]; } elsif ( ref ( $ref ) eq 'ARRAY' ) { $ra = $ref; } if ( ! defined $ra && defined $notArefMessage ) { print "$notArefMessage\n"; #print Dumper( $ref ); #$ra = []; } return $ra; } =head2 scalarMakeSureItsArray() Argument: 1. [R] Either an array ref or a scalar Returns: an array ref Description: If there are multiple elements with a same name, XML:Simple return them as a reference to an array, otherwise it returns a value. The problem is that for some XML document, the element appears only once in the structure, while for other documents, more than once. Here, we make sure that such elements are returned in a array even in cases when there is only one element. Obviosly we do this only for elements that we know can be returned as an array. =cut sub scalarMakeSureItsArray { my $ref = shift; my $ra; if ( defined $ref ) { if ( ref ($ref) ne 'ARRAY' ) { $ra= [ $ref]; } else { $ra= $ref; } } return $ra; } sub getDocumentation { my $self = shift; return $self->{'sDocumentation'}; } sub setDocumentation { my $self = shift; $self->{'sDocumentation'} = shift; } sub getAdditionalAppInfo { my $self = shift; return $self->{'sAdditionalAppInfo'}; } sub setAdditionalAppInfo { my $self = shift; $self->{'sAdditionalAppInfo'} = shift; } sub getCallsInfo { my $self = shift; return $self->{'raCallInfo'}; } sub setCallsInfo { my $self = shift; $self->{'raCallInfo'} = shift; } sub getCallsInfoInput { my $self = shift; return $self->_getCallsInfoByType('isInputArgument'); } sub getCallsInfoOutput { my $self = shift; return $self->_getCallsInfoByType('isOutputProperty'); } sub _getCallsInfoByType { my $self = shift; my $sTestMethodName = shift; my $raCallsInfo = $self->getCallsInfo(); my @arr = (); foreach my $pCallInfo (@$raCallsInfo) { my $isType = $pCallInfo->$sTestMethodName(); if ( $isType ) { push @arr, $pCallInfo; } } return \@arr; } # # END package 'eBay::API::XML::Tools::CodeGen::XSD::Annotation' # # # package eBay::API::XML::Tools::CodeGen::XSD::Annotation::CallInfo; # =pod =head1 eBay::API::XML::Tools::CodeGen::XSD::Annotation::CallInfo Manage call information for the annotations. =cut package eBay::API::XML::Tools::CodeGen::XSD::Annotation::CallInfo; use Data::Dumper; =head2 new() Arguments: a hash containing the following values: 1. 'CallName' and 'Context' are arrays 'Returned' => 'Conditionally', 'CallName' => [ 'GetBidderList', 'GetItem', ], 'Context' => [ 'BidList', 'LostList', 'WonList' ] 'MaxLength' => '32', 2. 'CallName' and 'Context' are just scalars 'Returned' => 'Conditionally', 'CallName' => 'GetBidderList', 'MaxLength' => '32', 'Context' => 'WatchList' NOTE: 'CallName' and 'Context' can be either an array or a scalar. This structure has to be converted into a set with 3 elements: a) callNames - array ref b) attributes - hash ref ('RequiredInput', 'Returned', 'MaxLength') c) context - array ref =cut sub new { my $classname = shift; my %args = @_; my $self = {}; bless( $self, $classname ); # 1. initialize object properties my $raCallNames = undef; my $raContext = undef; my $rhAttributes = {}; # 1.1 callNames my $refCallNames = $args{'CallName'}; $raCallNames = eBay::API::XML::Tools::CodeGen::XSD::Annotation::scalarMakeSureItsArray ( $refCallNames ); # 1.2. find attributes my @keys = keys %args; foreach my $key (@keys) { if ( $key ne 'CallName' && $key ne 'Context' ) { $rhAttributes->{ $key } = $args{$key}; } } # 1.3. context(s) my $refContext = $args{'Context'}; $raContext = eBay::API::XML::Tools::CodeGen::XSD::Annotation::scalarMakeSureItsArray ( $refContext ); $self->_setCallNames( $raCallNames ); $self->_setAttributes( $rhAttributes); $self->_setContext( $raContext ); $self->_setIsInputArgument ( 0 ); $self->_setIsOutputProperty ( 0 ); # 2. Run additional logic to separate Input/Output arguments/properties # from the rest of attributes # Set type!! # # Type can have two values: 'input' or 'output' # 'input' - property is used as an argument for listed calls # 'output' - property is being retrieved by listed calls # RequiredInput - input argument # Returned - output property my $isTypeSet = 0; if ( exists ($rhAttributes->{'RequiredInput'}) ) { $self->_setIsInputArgument ( 1 ); $isTypeSet = 1; } if ( exists ( $rhAttributes->{'Returned'} ) ) { $self->_setIsOutputProperty ( 1 ); $isTypeSet = 1; } if ( ! $isTypeSet ) { my @keys = keys %$rhAttributes; #print Dumper($rhAttributes); #print "Annotations: cannot determine type from following keys:\n" # . "\t" . join (',', @keys); } return $self; } =head2 _setCallNames() Arguments: a reference to an array containg scalars (strings) Description: Array contains names of calls for which the given property is used. =cut sub _setCallNames { my $self = shift; $self->{'raCallNames'} = shift; } =head2 getCallNames() Returns: a reference to an array containg scalars (strings) Description: Array contains names of calls for which the given property is used. =cut sub getCallNames { my $self = shift; return $self->{'raCallNames'}; } =head2 _setAttributes() Arguments: a reference to a hash. Hash element definition: key - attribute name (scalar string value - attribute value (scalar string) Description: Hash contains all attributes common for all calls defined with current instance of CallInfo =cut sub _setAttributes { my $self = shift; $self->{'rhAttributes'} = shift; } =head2 getAttributes() Arguments: a reference to a hash. Description: see getAttributes =cut sub getAttributes { my $self = shift; return $self->{'rhAttributes'}; } =head2 _setContext() Arguments: a reference to an array containg scalars (strings) Description: Array contains context names in which the given property is being used. =cut sub _setContext { my $self = shift; $self->{'raContext'} = shift; } =head2 getContext() Returns: a reference to an array containg scalars (strings) Description: Array contains context names in which a given property is being used. =cut sub getContext { my $self = shift; return $self->{'raContext'}; } =head2 getInputOutputAttribute() Returns: reference to a hash The hash contains either all Input/Output attributes of all calls contained in this CallInfo instance. Attribute can be both, input argument and output property 1. For calls where the property is used as an input argument return: 'RequiredInput' attribute name 2. For calls where the property is used as an output property return: 'Returned' attribute name =cut sub getInputOutputAttributeName { my $self = shift; my $rhAttributes = $self->getAttributes(); my @aInputOutputAttrNames = ('RequiredInput', 'Returned'); my %hIO = (); foreach my $attrName ( @aInputOutputAttrNames ) { $hIO { $attrName } = $rhAttributes->{$attrName }; } return \%hIO; } =head2 getNonInputOutputAttribute() Returns: reference to a hash The hash contains all attributes except Input/Output related attributes. See 'getInputOutputAttribute' method above for definition of Input/Output attributes. =cut sub getNonInputOutputAttributes { my $self = shift; my $rhInputOutputAttributes = $self->getInputOutputAttributeName(); my $rhAllAttributes = $self->getAttributes(); my %hNonIO = %$rhAllAttributes; if ( defined $rhInputOutputAttributes ) { foreach my $key ( keys %$rhInputOutputAttributes ) { delete $hNonIO{$key}; } } return \%hNonIO } sub _setIsInputArgument { my $self = shift; $self->{'isInputArgument'} = shift; } sub isInputArgument { my $self = shift; return $self->{'isInputArgument'}; } sub _setIsOutputProperty { my $self = shift; $self->{'isOutputProperty'} = shift; } sub isOutputProperty { my $self = shift; return $self->{'isOutputProperty'}; } 1;