#####################################################################
#####################################################################
## Copyright 2005 Steven R. Newcomb                                ##
##                                                                 ##
## This software is licensed under the Apache License, Version 2.0 ##
## (the "License"); you may not use this file except in compliance ##
## with the License.  Copies of the License are available at       ##
##        http://www.apache.org/licenses/LICENSE-2.0               ##
##                                                                 ##
## Unless required by applicable law or agreed to in writing,      ##
## software distributed under the License is distributed on an "AS ##
## IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ##
## express or implied.  See the License for the specific language  ##
## governing permissions and limitations under the License.        ##
##                                                                 ##
## Copies of the source code and the license are available at      ##
##        http://www.versavant.org                                 ##
#####################################################################
#####################################################################

####################################
cvsRevision = "$Revision: 1.4 $" ##
####################################


#####################################################################
## Implementation of the conferral rule "inferFromBookProperties"
## in Topic Map Application "TMA1", used in versavant/demo.py 
#####################################################################


import sys, re  ## Python system module and Python regular expression module

def inferFromBookProperties( vsv, conferralRule):


## For each proxy for a subject which is a book (i.e., for each proxy that has a
## "Book' property, here locally called "existingBookProxy"):

## * If it has a PublisherName property, create a proxy (here locally
##   called "publisherProxy") for the publisher.  The SIP of this proxy
##   will be a PersonOrOrganization property whose value is the
##   publisher's name.

## * Parse the value of the Book property, which will have the form:
##   '(book title) by (book author)', so that we have the title and author
##   separately.
  
## * Create a proxy (here locally called "authorProxy") for the author.
##   The SIP of this proxy will be a PersonOrOrganization property whose
##   value is the author's name.

## * Create an additional proxy (here called "newBookProxy") for the
##   book, so we can add properties to it.  (The existing proxy isn't in
##   play any more and can't be changed.  But we can create a new proxy
##   for the same subject and add properties to it, in the expectation
##   that the new proxy will merge with the old proxy.)

## * To the authorProxy, add a BooksAuthored property whose value is a
##   reference to newBookProxy (in the end, the same effect would be
##   realized by making the value a reference to existingBookProxy -- it
##   really doesn't matter which one we refer to, because both proxies
##   represent exactly the same subject).

## * To the publisherProxy, add a BooksPublished property whose value is
##   a reference to newBookProxy (same comment applies here).

## * To the newBookProxy, also add:

##     - a BookTitle property whose value is the book's title.  This will
##       allow us to look up the book by its title.

##     - a BookAuthorNames property whose value is the name of the
##       author.

##     - a BookAuthors property whose value is/are reference(s) to the
##       authorProxies

##     - a BookPublisher property whose value is a reference to the
##       publisherProxy



    topicMap = conferralRule.topicMap  ## make the below code less verbose (and maybe run slightly faster)
    TMA = conferralRule.TMA
    TMAName = TMA.TMAName
    
    existing_Book_SIPValueList = TMA.propertyClasses[ 'Book'].overheadDict.keys()
    ## existing_Book_SIPValueList is now a list of all the different
    ## values of all the Book properties in the topic map.

    ## (Reminder: Since 'Book' properties are SIPs, each such value
    ## indicates the subject of the proxy in which it appears; that
    ## subject is always a book, presumably.)

    for existing_Book_SIPValue in existing_Book_SIPValueList:
        existingBookProxyIds = TMA.propertyClasses[ 'Book'].overheadDict[ existing_Book_SIPValue]
            ## existingBookProxyIds is now a list of all the proxyIds
            ## that have a specific value (existing_Book_SIPValue).
            ## In other words, it's a list of the proxyIds of all the
            ## proxies of a specific book.

            ## (Reminder: a proxyId is a string of decimal digits with
            ## two leading zeroes.  It is the serial number -- the
            ## unique identifier -- of a subject proxy.)

        for existingBookProxyId in existingBookProxyIds:

            existingBookProxy = topicMap.proxies[ existingBookProxyId]
            ## We've just converted the proxyId into the corresponding Versavant ProxyObject instance.


            bookPropertyInProxyKey = vsv.catKey( TMAName, 'Book')
            ## Within a proxy, each property instance has a unique
            ## name which is formed by concatenating the TMA name, a
            ## field separator, and the property class name.  Now,
            ## bookNamePropertyInProxyKey == 'TMA1,,Book'

            bookPropertyInstance = existingBookProxy.propertyInstances[ bookPropertyInProxyKey]
            ## bookPropertyInstance is now the Versavant
            ## PropertyInstance object that represents the
            ## 'TMA1,,Book' property instance.

            #######################################################

            ## This particular conferral rule confers proxies into
            ## existence that have the same feature -- a "Book" SIP
            ## instance -- that trigger it.  Therefore, in order to
            ## avoid an endless loop, this conferral rule must detect
            ## when it is being triggered by its own output, and
            ## prevent such self-induced conferral operations.  If we
            ## fail to control this kind of situation here in the
            ## conferral rule itself, Versavant will detect the
            ## looping condition and abort itself with an appropriate
            ## (and characteristically verbose) error message.

            ## One way for the conferral rule to determine whether
            ## it's being triggered by its own output need to look at
            ## Versavant's auditing records for the property that
            ## we're regarding as the triggering property.  The
            ## following code does that:

            conferralRuleTriggerRoleId = vsv.catKey( conferralRule.conferralRuleKey, 'book identifier')
            if bookPropertyInstance.conferralTriggerAncestry.has_key( conferralRuleTriggerRoleId):
                continue  ## This property instance was created by this rule, so abort
                          ## the rest of this for loop -- don't be triggered.

            #######################################################

            ## In order to help Versavant decide when to stop invoking
            ## this conferral rule, we now need to record that the
            ## property instance we're interested in, here, will have
            ## triggered a conferral operation.

            triggers = {}  ## new dictionary to work in
            triggers[ 'book identifier'] = bookPropertyInstance.propertyInstanceKey
            ## 'book identifier' is the string that this conferral
            ## rule uses as the name of the "conferral triggering
            ## role" that this property instance is about to play in
            ## this conferral operation.

            ## bookPropertyInstance.propertyInstanceKey is the unique
            ## identifier of this property instance in this topic
            ## map.  It has three fields.  The first field is the
            ## proxy Id, the second field is the TMA name, and the
            ## third field is the property class name, e.g.:
            ## 00112,,TMA1,,Book

            ## So, we've just made preparations to record that, for
            ## example, property instance "00112,,TMA1,,Book" is
            ## playing the "book identifier" conferral triggering role
            ## in this conferral operation.
            
            #######################################################

            ## For auditing reasons, we also need to record the
            ## sources of the data that we are going to confer.

            conferredValueSources = {}  ## new dictionary to work in
            conferredValueSources[ 'book stuff'] = bookPropertyInstance.propertyInstanceKey
            ## The triggering property also happens to be one of the
            ## data sources.  'book stuff' is the name that this
            ## conferral rule uses as the name of a "property value
            ## source" role.

            publisherNamePropertyInstance = None
            publisherNamePropertyInProxyKey = vsv.catKey( TMAName, 'BookPublisherName')
            if existingBookProxy.propertyInstances.has_key( publisherNamePropertyInProxyKey):
            ## We're seeing whether the proxy has a PublisherName property.

                publisherNamePropertyInstance = existingBookProxy.propertyInstances[ publisherNamePropertyInProxyKey]
                conferredValueSources[ 'publisher stuff'] = publisherNamePropertyInstance.propertyInstanceKey
                ## If the PublisherName property exists, its value
                ## will play the 'publisher stuff' role in the
                ## conferral operation.

            #######################################################

            if vsv.conferralMayBeSignificant( conferralRule, triggers, conferredValueSources):
            ## Now we're asking Versavant whether we should go ahead
            ## and do the conferral.  Versavant will say "yes" (return
            ## True) unless the conferral operation will definitely
            ## not add anything new to the topic map.

                ## Now we can confer, confer, confer!

                if publisherNamePropertyInstance:
                    publisherName = publisherNamePropertyInstance._value
                    publisherProxy = vsv.newProxyObject( topicMap)  ## create a proxy for the publisher of the book

                    ## create a property instance in publisherProxy: its SIP
                    vsv.newPropertyInstance(
                        publisherProxy,          ## the ProxyObject we're adding the property instance to
                        TMA,                     ## the TMA that defines the property class of the new property instance
                        'PersonOrOrganization',  ## the name of the property class of the new property instance
                        vsv.versaCopy( publisherName),           ## the value of the new property.  Must be copied; can't refer!
                        triggers,                ## the "triggers" information
                        conferredValueSources)   ## the "property value sources" information
                    

                ## Parse the "Book" property's value.  Kind of a dumb
                ## example, I guess, but it shows flexibility.
                ## Conferral rules, like all other aspects of TMAs,
                ## and like Original Population Tasks, can do anything
                ## that software can do.
                compiledRegularExpressionObject = re.compile( '(^.+)( by )(.+$)')
                matchObject = compiledRegularExpressionObject.match( bookPropertyInstance._value)
                bookTitle = matchObject.group( 1)   ## now we know the title of the book
                bookAuthorName = matchObject.group( 3)  ## now we know the author of the book
                bookAuthorNameList = bookAuthorName.split( ',')  ## we're assuming, naively, commas between the author names, here

                ## make a proxy for each author, and set its SIP
                authorProxyList = []  ## create an empty list 
                authorProxyIdList = []
                for bookAuthorName in bookAuthorNameList:
                    authorProxy = vsv.newProxyObject( topicMap)
                    authorProxyList.append( authorProxy)  ## making a list of ProxyObjects, here
                    authorProxyIdList.append( authorProxy.proxyId)  ## making a list of subject proxy ids, here
                    vsv.newPropertyInstance( authorProxy, TMA, 'PersonOrOrganization', bookAuthorName, triggers, conferredValueSources)

                ## make another proxy for the book -- one we can add properties to -- and set its SIP
                newBookProxy = vsv.newProxyObject( topicMap) 
                vsv.newPropertyInstance( newBookProxy, TMA, 'Book', vsv.versaCopy( bookPropertyInstance._value), triggers, conferredValueSources)
                
                ## set the BookTitle property of the book proxy
                vsv.newPropertyInstance( newBookProxy, TMA, 'BookTitle', bookTitle, triggers, conferredValueSources)
                
                ## set the BookAuthorNames property of the book proxy
                vsv.newPropertyInstance( newBookProxy, TMA, 'BookAuthorNames', bookAuthorNameList, triggers, conferredValueSources)

                ## set the BookAuthors property of the book proxy
                vsv.newPropertyInstance( newBookProxy, TMA, 'BookAuthors', authorProxyIdList, triggers, conferredValueSources)

                ## set the BooksAuthored property of the author proxy
                vsv.newPropertyInstance( authorProxy, TMA, 'BooksAuthored', [ existingBookProxy.proxyId], triggers, conferredValueSources)
                                                                        ## doesn't matter whether we refer to the new or
                                                                        ## the existing book proxy.

                if publisherNamePropertyInstance:

                    ## set the BookPublisher property of the book proxy
                    vsv.newPropertyInstance( newBookProxy, TMA, 'BookPublisher', [ publisherProxy.proxyId], triggers, conferredValueSources)

                    ## set the BooksPublished property of the publisher proxy
                    vsv.newPropertyInstance( publisherProxy, TMA, 'BooksPublished', [ newBookProxy.proxyId], triggers, conferredValueSources)
