#!/usr/bin/perl -w # ################################################################################ # Module: ............... eBay/API/XML/Tools/CodeGen/XSD # File: ................. BaseCodeGenDataType.pm # Original Author: ...... Milenko Milanovic # Last Modified By: ..... Robert Bradley / Jeff Nokes # Last Modified: ........ 03/30/2007 @ 23:08 ################################################################################ package eBay::API::XML::Tools::CodeGen::XSD::BaseCodeGenDataType; # Global Variables our $VERSION = '0.01'; # The version of this module. =pod =head1 eBay::API::XML::Tools::CodeGen::XSD::BaseCodeGenDataType Base class for generating the data type classes. =cut use lib '../../../../../../'; # To get access to all packages in XSD directory use strict; use warnings; use Exporter; use IO::File; use File::Spec; use File::Path 'mkpath'; use Time::localtime; use Data::Dumper; use Scalar::Util 'blessed'; use eBay::API::XML::Tools::CodeGen::XSD::Annotation; my $gsRootPackageName = undef; ## hash that provides typeNS to DataType class mapping my %ghDataTypeClasses = (); # # key = typeNS, value = class instance my $gsReleaseNumber; my $gsReleaseType = ''; # # this class should extend: CodeGenRequestResponseType.pm # sub new { my $classname = shift; my $rhXmlSimple = shift; my $self = {}; bless($self, $classname); $self->{'name'} = undef; $self->{'rhAnnotation'} = undef; $self->{'raElements'} = []; $self->{'raAttributes'} = []; $self->{'raEnums'} = []; $self->{'fullPackageName'} = ''; # 'typeNS' is a key to find a property's full package name $self->{'typeNS'} = undef; # All Core DataTypes have 'hasIsScalarMethod' # Request and Response Data Types do not have 'hasIsScalarMethod' $self->{'hasIsScalarMethod'} = 1; # Only Enum DataTypes are considered to be scalars # All other types have to be instantiated $self->{'isScalar'} = 0; $self->_init( $rhXmlSimple ); return $self; } sub setReleaseNumber { $gsReleaseNumber = shift; } sub setReleaseType { $gsReleaseType = shift; } sub setTypeNSToGenDataTypeMapping { my $rhTypeNStoClasses = shift; %ghDataTypeClasses = %$rhTypeNStoClasses; } sub getDataTypeByDataTypeWithNS { my $self = shift; my $typeNS = shift; my $pGenClassInstance = $ghDataTypeClasses{ $typeNS }; #print "typeNS=|$typeNS|, pGenClassInstance=|$pGenClassInstance|\n"; return $pGenClassInstance; } sub getRootPackageName { my $self = shift; if ( ! defined $gsRootPackageName ) { print "BaseCodeGenDataType::getRootPackageName failed:\n" . " $gsRootPackageName must be set!\n"; exit; } return $gsRootPackageName; } sub setRootPackageName { $gsRootPackageName = shift; } sub _init { my $self = shift; my $rh= shift; # 1. name my $name = $rh->{'name'}; $self->setName($name); my $fullPackageName = $self->_determineFullPackageName(); $self->setFullPackageName ( $fullPackageName ); $self->setTypeNS ( 'ns:' . $name ); # 2. Annotations my $rhAnnotation = $rh->{'xs:annotation'}; my $pAnnotation; if ( defined $rhAnnotation ) { $pAnnotation = eBay::API::XML::Tools::CodeGen::XSD::Annotation->new ( $rhAnnotation ); } $self->setAnnotation( $pAnnotation ); # 3. superclass name and properties $self->_initElementsAndAttributes( $rh ); } sub getName { my $self = shift; return $self->{'name'}; } sub setName { my $self = shift; $self->{'name'} = shift; } sub getAnnotation { my $self = shift; return $self->{'pAnnotation'}; } sub setAnnotation { my $self = shift; $self->{'pAnnotation'} = shift; } sub getSuperclassName { my $self = shift; return $self->{'superclassName'}; } sub setSuperclassName { my $self = shift; $self->{'superclassName'} = shift; } sub getElements { my $self = shift; return $self->{'raElements'}; } sub setElements { my $self = shift; my $ra = shift; $self->{'raElements'} = _sortElementArray ($ra); } sub getAttributes { my $self = shift; return $self->{'raAttributes'}; } sub setAttributes { my $self = shift; my $ra = shift; $self->{'raAttributes'} = _sortElementArray ($ra); } sub getEnums { my $self = shift; return $self->{'raEnums'}; } sub setEnums { my $self = shift; $self->{'raEnums'} = shift; } # # derived properties # sub getDocumentation { my $self = shift; my $rhAnnotation = $self->getAnnotation(); my $documenation = ''; if ( defined $rhAnnotation ) { $documenation = $rhAnnotation->{'xs:documentation'}; } } sub setFullPackageName { my $self = shift; $self->{'fullPackageName'} = shift; } sub getFullPackageName { my $self = shift; return $self->{'fullPackageName'}; } sub getTypeNS { my $self = shift; return $self->{'typeNS'}; } sub setTypeNS { my $self = shift; $self->{'typeNS'} = shift; } sub hasIsScalarMethod { my $self = shift; return $self->{'hasIsScalarMethod'}; } sub setHasIsScalarMethod { my $self = shift; $self->{'hasIsScalarMethod'} = shift; } sub isScalar { my $self = shift; return $self->{'isScalar'}; } sub setScalar { my $self = shift; $self->{'isScalar'} = shift; } # # auxilary methods # sub _sortElementArray { my $ra = shift; if ( ! defined $ra ) { return $ra; } my @arr = sort { $a->getName() cmp $b->getName() } @$ra; return \@arr; } =head2 getGetterName() =cut sub getGetterName { my $self = shift; my $propName = shift; my $typeNS = shift; if ( ! defined $typeNS ) { print "propName=|" . $propName . ", typeNS not defined, aborting..\n"; exit; } my $sGetterName = "\u$propName"; if ($typeNS eq 'xs:boolean') { $sGetterName = "is$sGetterName"; } else { $sGetterName = "get$sGetterName"; } return $sGetterName; } =head2 getSetterName() =cut sub getSetterName { my $self = shift; my $propName = shift; my $name = "set\u$propName"; return $name; } =head2 _getSetterComment() This method is used from within 'getSetterGetterComment' method =cut sub _getSetterComment { my $self = shift; my %args = @_; my $propertyType = $args{'propertyType'}; my $isArray = $args{'isArray'}; my $pAnnotation = $args{'pAnnotation'}; # 1. annotation documentation # my $annDoc = $self->_formatDocumentation( $pAnnotation ); # 2. annotation callInfo documentation (input arguments) # my $strCallInfoInputArgs = $self->_getSetterCallsInfo( $pAnnotation ); # 3. type description # my $typeComment = $self->_getTypeComment( 'isArray' => $isArray ,'propertyType' => $propertyType ,'sDescriptionKey' => 'Argument' ); # 4. additional app info in annotations # very rarely used # my $sAdditionalAppInfo = $self->_getAdditionalAppInfo( $pAnnotation ); my $strComment = $annDoc; $strComment .= $sAdditionalAppInfo; $strComment .= $strCallInfoInputArgs; $strComment .= $typeComment; return $strComment; } sub _getAdditionalAppInfo { my $self = shift; my $pAnnotation = shift; my $str = ''; if ( defined $pAnnotation ) { $str = $pAnnotation->getAdditionalAppInfo(); if ( length($str) > 0 ) { $str = "$str\n"; } } return $str; } sub _getSetterCallsInfo { my $self = shift; my $pAnnotation = shift; my $strCallInfoInputArgs = ''; if ( defined $pAnnotation ) { my $raCallInfoInput = $pAnnotation->getCallsInfoInput(); $strCallInfoInputArgs = $self->getPropertyToCallInfo( $raCallInfoInput ); } return $strCallInfoInputArgs; } =head2 _getGetterComment() This method is used from within 'getSetterGetterComment' method =cut sub _getGetterComment { my $self = shift; my %args = @_; my $propertyType = $args{'propertyType'}; my $isArray = $args{'isArray'}; my $pAnnotation = $args{'pAnnotation'}; # 1. annotation callInfo documentation (input arguments) # my $strCallInfoOutputProps = $self->_getGetterCallsInfo( $pAnnotation ); # 2. type description # my $typeComment = $self->_getTypeComment( 'isArray' => $isArray ,'propertyType' => $propertyType ,'sDescriptionKey' => 'Returns' ); my $strComment = ''; $strComment .= $strCallInfoOutputProps; $strComment .= $typeComment; return $strComment; } sub _getGetterCallsInfo { my $self = shift; my $pAnnotation = shift; my $strCallInfoOutputProps = ''; if ( defined $pAnnotation ) { my $raCallInfoInput = $pAnnotation->getCallsInfoOutput(); $strCallInfoOutputProps = $self->getPropertyToCallInfo( $raCallInfoInput ); } return $strCallInfoOutputProps; } =head2 getTypeComment() Used in _getGetterComment and in _getSetterComment methods =cut sub _getTypeComment { my $self = shift; my %args = @_; my $isArray = $args{'isArray'}; my $typeNS = $args{'propertyType'}; my $sDescKey = $args{'sDescriptionKey'}; my $typeComment = ''; if ( $isArray ) { # array property $typeComment =<<"ARR_COMMENTS_GETTER_x"; # $sDescKey: reference to an array of '$typeNS' ARR_COMMENTS_GETTER_x } else { # scalar property $typeComment =<<"SCALAR_COMMENTS_GETTER_x"; # $sDescKey: '$typeNS' SCALAR_COMMENTS_GETTER_x } return $typeComment; } =head2 getSetterGetterComment() =cut sub getSetterGetterComment() { my $self = shift; my %args = @_; my $propertyType = $args{'propertyType'}; my $isArray = $args{'isArray'}; my $pAnnotation = $args{'pAnnotation'}; my $methodName = $args{'methodName'}; my $setterOrGetter = $args{'setterOrGetter'}; my $strComment = ''; if ( $setterOrGetter eq 'getter' ) { $strComment = $self->_getGetterComment( 'propertyType' => $propertyType ,'isArray' => $isArray ,'pAnnotation' => $pAnnotation ); } else { $strComment = $self->_getSetterComment( 'propertyType' => $propertyType ,'isArray' => $isArray ,'pAnnotation' => $pAnnotation ); } my $str = <<"SET_COMMENTS"; =head2 $methodName() $strComment =cut SET_COMMENTS return $str; } =head2 getPropertyToCallInfo() CallInfo is a package defined in eBay::API::XML::Tools::CodeGen::XSD::Annotation.pm file. The packageName is: eBay::API::XML::Tools::CodeGen::XSD::Annotation::CallInfo =cut sub getPropertyToCallInfo { my $self = shift; my $raCallInfo = shift; my $str = ''; if ( defined $raCallInfo ) { foreach my $pCallInfo (@$raCallInfo ) { $str .= $self->getPropertyToCallInfo_Calls( $pCallInfo ); $str .= $self->getPropertyToCallInfo_Attributes( $pCallInfo ); $str .= $self->getPropertyToCallInfo_Context( $pCallInfo ); $str .= "\n"; } } return $str; } sub getPropertyToCallInfo_Calls { my $self = shift; my $pCallInfo = shift; my $str = " Calls: "; my $raCallNames = $pCallInfo->getCallNames(); my $cnt = 0; foreach my $callName (@$raCallNames) { if ( $cnt != 0 ) { $str .= " "; } $str .= "$callName\n"; $cnt++; } return $str; } sub getPropertyToCallInfo_Attributes { my $self = shift; my $pCallInfo = shift; my $str = ''; my @arr = ( $pCallInfo->getInputOutputAttributeName() ,$pCallInfo->getNonInputOutputAttributes() ); foreach my $rhAttrs ( @arr ) { foreach my $key ( sort keys %$rhAttrs) { my $value = $rhAttrs->{$key}; if ( defined $value ) { my $strKey = " $key: "; my $sValue = ''; # in some cases - value is a hash reference # so print whatever is in that hash. Usually it is an empty hash if ( ref($value) eq 'HASH' ) { my ($sLev2Key, $sLev2Val); while (($sLev2Key, $sLev2Val) = ( each %$value )) { if ($sValue ne '') { $sValue .= ', '; } $sValue .= "$sLev2Key: $sLev2Val"; } } elsif ( ref($value) eq 'ARRAY' ) { my $sCntTmp = 0; my $sTmpLen = length($strKey); my $template = "A$sTmpLen"; my $ident = pack($template,''); foreach my $sTmp ( @$value) { if ( $sCntTmp > 0 ) { $sValue .= $ident; } $sValue .= "$sTmp\n"; $sCntTmp++; } } else { $sValue = $value; } $str .= "$strKey$sValue\n"; } } } return $str; } sub getPropertyToCallInfo_Context { my $self = shift; my $pCallInfo = shift; my $raContext = $pCallInfo->getContext(); my $str = ""; if ( defined $raContext ) { my $cnt = 0; $str .= " Context: "; foreach my $sContext ( @$raContext ) { if ( $cnt != 0 ) { $str .= " "; } $str .= "$sContext\n"; $cnt++; } } return $str; } sub trim { my $str = shift; $str =~ s/^\s+//g; $str =~ s/\s+$//g; return $str; } sub _formatDocumentation { my $self = shift; my $pAnnotation = shift; my $annDoc = ''; if ( defined $pAnnotation ) { $annDoc = $pAnnotation->getDocumentation(); if ( ! defined $annDoc ) { $annDoc = ''; } else { if ( ref($annDoc) eq 'HASH' ) { $annDoc = Dumper( $annDoc ); #return $annDoc; } } $annDoc = trim ($annDoc); $annDoc =~ s/^\s+//gms; ## keep this if we decide to wrap paragraphs #$Text::Wrap::columns = 80; #my @text = split(/\n/, $str); #$str = wrap('', '', @text); if ( length($annDoc) > 0 ) { $annDoc .= "\n\n"; } } return $annDoc; } sub validateType { my $self = shift; my $type = shift; # exceptions # some types in properties do not start with "ns:" # so I am fixing that here if ( ($type =~ m/Type$/) && ! ($type =~ m/^ns:/) ) { $type = "ns:$type"; } return $type; } sub _initElementsAndAttributes { my $self = shift; my $packageName = blessed $self; print "_initElementsAndAttributes must be implemented in package" . " $packageName\n"; exit; } sub _determineFullPackageName { my $self = shift; my $packageName = blessed $self; print "_determineFullPackageName must be implemented in package" . " $packageName\n"; exit; } =head2 _getClassBody() Must be implemented in each class that extends this class =cut sub _getClassBody { my $self = shift; my $packageName = blessed $self; print "_getClassBody must be implemented in package" . " $packageName\n"; exit; } sub _getRelativeOutputDir { my $self = shift; my $fullPackageName = shift; my @dirs = split(/::/, $fullPackageName ); # remove class name from the path. my $latsNdx = $#dirs; $#dirs = $latsNdx - 1; my $path = File::Spec->catdir(@dirs); return $path; } sub genCode { my $self = shift; my %args = @_; my $rootOutputDir = $args{'rootOutputDir'}; my $fullPackageName = $self->getFullPackageName(); my $relOutputDir = $self->_getRelativeOutputDir( $fullPackageName ); my $outputDir = File::Spec->catdir( $rootOutputDir, $relOutputDir); my $classHeader = $self->_genClassHeader(); my $classBody = $self->_getClassBody(); my $classFooter = $self->_genClassFooter(); $classBody = $classHeader . "\n" . $classBody . "\n" . $classFooter; my $sClassFileName = $self->_getFileName(); $self->createDirIfNotExists( $outputDir ); $self->writeFile ( 'outputDir' => $outputDir ,'outputFileName'=> $sClassFileName ,'sContent' => $classBody ); } sub _getFileName { my $self = shift; my $sClassFileName = $self->getName() . '.pm'; return $sClassFileName; } sub _genClassFooter { return <<"FOOTER"; 1; FOOTER } sub _genClassHeader { my $self = shift; my $rootPackage = $self->getRootPackageName(); my @arr = split(/::/, $rootPackage); my $packagePath = File::Spec->catdir (@arr); my $fileName = $self->_getFileName(); my $fullPackageName = $self->getFullPackageName(); my $tm = localtime; my $timeStamp = sprintf("%02d/%02d/%04d %02d:%02d" ,$tm->mon+1 ,$tm->mday ,$tm->year+1900 ,$tm->hour ,$tm->min ); my $pAnnotation = $self->getAnnotation(); my $sDescription = ''; if ( defined $pAnnotation ) { $sDescription = $self->_formatDocumentation( $pAnnotation ); } my $str = <<"HEADER"; #!/usr/bin/perl package $fullPackageName; use strict; use warnings; ########################################################################## # # Module: ............... $packagePath # File: ................. $fileName # Generated by: ......... genEBayApiDataTypes.pl # Last Generated: ....... $timeStamp # API Release Number: ... $gsReleaseNumber # ########################################################################## =head1 $fullPackageName $fullPackageName =head1 DESCRIPTION $sDescription =head1 SYNOPSIS =cut HEADER return $str; } sub writeFile { my $self = shift; my %args = @_; my $outputDir = $args{'outputDir'}; my $outputFileName = $args{'outputFileName'}; my $sContent = $args{'sContent'}; my $out_filename = File::Spec->catfile($outputDir, $outputFileName); if ( -e $out_filename ) { # check out if the file exists if ( ! (-W $out_filename) ) { print "File $out_filename is not writtable\n"; print "Please verify that the file is checked out from SCM!\n"; print "Aborting script execution!\n"; exit; } } my $out_fh = IO::File->new( "> $out_filename"); if ( ! defined $out_fh ) { my $error = $!; print "Could not create file '$out_filename', error=|$error|\n"; print "Aborting script execution!\n"; exit 1; } print $out_fh $sContent; $out_fh->close(); } sub createDirIfNotExists { my $self = shift; my $outputDir = shift; # create output directory if it does not exist! my $isOutputDirExist = (-e $outputDir); if ( ! defined $isOutputDirExist ) { # if the output directory does not exist # create it. #print "outputDir =|$outputDir| does not exist\n"; mkpath($outputDir); my $error = $!; # For some reason, even though the directory is being created # I get 'failed to create directory ... No such file or directory' # error. # see: http://perldoc.perl.org/File/Path.html # "On Windows, if mkpath gives you the warning: # No such file or directory, this may mean that you've exceeded # your filesystem's maximum path length." # So if we get this error, we will just ignore it if ( $error && $error ne 'No such file or directory') { print "failed to create directory |$outputDir|, error=|$error|\n"; exit 1; } } } 1;