#!/usr/bin/env python
#
# This file is part of the Coral project.
# Please read the copyright notice in the LICENSE file.
#
# xmidimapobsgen.py - Generates an observer that listens to properties specified in DIML models
#
# SYNOPSIS
#
# xmidimapobsgen [-o outputfile] models
#
#
# DESCRIPTION
#
# models: One or more DIML models describing the XMI-DI hierarchy of model elements
# 
# outputfile: The file where the output code is written. If none specified, the output
# is written to stdout.
# 
# xmidisyncobsgen reads the models containing the XMI-DI hierarchy and collects properties
# from the specifications that should be updated in a synchronization. The output file
# is an observer that calls the synchronizeContents of a class generated with diml2xmidigen
# 
# OPTIONS
# -h, --help : help
# -o "outputfile": Writes the output to "outputfile". If omitted, output is directed to stdout
#
# EXAMPLES
#
# xmidisyncobsgen ClassDiagram.xml xmidisyncobserver.py
# 
# AUTHORS
#
# Coral Team - 2004-2005
import coral,re,string,sys,getopt
from coral.lgen.codeartifact import CodeArtifact

DI = coral.lang.XMIDI20
DIML = coral.lang.DIML10


def gen_property_strings(element,ca):

    properties=[]
    
    mappings = element.__getSlotByOppositeName__(DIML.ElementToXMIDIMapping, "element")

    p = element.package
    lang = unicode(p.name)
    ver = unicode(p.version)
    rev = unicode(p.revision)

    classname = unicode(element.name)
    
    for m in mappings:
        assert m.root
        root = m.root
        
        children = [c for c in root.children]

        ## Look for connector-properties that we should listen to
        if root.oclIsKindOf(DIML.GraphEdgePart):
            for conn in root.connector:
                prop = conn.property
                assert prop
                properties.append(prop)

        ## This generates the properties that has a special listening                        
        if root.oclIsKindOf(DIML.GraphNodePart):
            for conn in root.proxyConnector:
                np = conn.nextProperty
                assert np
                properties.append(np)


        properties += recurse(children)

    prophash = {}
    for p in properties:
        prophash[p] = True

    for property in prophash.keys():
        #  for example coral.lang.UML14.Class.getProperty("stereotype")
        ca.add('coral.lang.'+lang+ver+rev+'.'+classname+'.getPropertyByName("' + unicode(property.name) + '"): 1,')
    if properties:
        ca.add("")
    return lang+ver+rev # UML14 for example

def recurse(containeds):
    if not containeds:
        return []

    properties={}
    to_recurse=[]
    for conn in containeds:
        to_recurse+= [conn.child]
        sel = conn.selection
        if sel:
            property = sel.property
            if property:
                properties[property]=True

    next_level=[]
    for i in to_recurse:
        if i.oclIsKindOf(DIML.ConcretePart):
            next_level+=[contained for contained in i.children]

    return properties.keys()+recurse(next_level)
    
def extractProperties(filenames,ca):

    langStrings=[]
    metaclasses=[]
    for filename in filenames:
        st=coral.loadDocument(filename)
        model = st.contents().select(lambda x: x.oclIsKindOf(DIML.MappingModel))[0]
        mappings = model.ownedElement.select(lambda x: x.oclIsKindOf(DIML.ElementToXMIDIMapping))
        
        for m in mappings:
            if m.element not in metaclasses:
                metaclasses.append(m.element)

    for metacls in metaclasses:                             
        langStr=gen_property_strings(metacls,ca)
        langStrings.append(langStr)

    return langStrings

def peekForLangName(filenames):
    if filenames:
        st=coral.loadDocument(filenames[0])
        model = st.contents().select(lambda x:x.oclIsKindOf(DIML.MappingModel))[0]
        dclass = model.ownedElement.select(lambda x:x.oclIsKindOf(DIML.ElementToXMIDIMapping))[0]

        metaclass = dclass.element
        p = metaclass.package
        return unicode(p.name)+unicode(p.version)+unicode(p.revision)


def usage():
    print "Usage: xmidimapobsgen inputmodels outputfile "
    print "    inputmodels: the name(s) of your MODEL2XMIDI model(s)"
    print "options:\n -h, --help : display this help"
    print "-o \"outputfile\": the filename for the output" 

def main(arguments):
    global ca
    try:
        opts, args = getopt.gnu_getopt(arguments, "ho:", ["help"])
    except getopt.GetoptError:
        # print help information and exit:
        usage()
        sys.exit(2)
    if len(args) < 1:
        usage()
        sys.exit(2)

    output = None

    for o, a in opts:
        if o in ("-h", "--help"):
            usage()
            sys.exit()
        if o in ("-o"):
            output = a
        
            
    inputmodels = args

    assert len(inputmodels) > 0
    ### Extract properties
    ca = CodeArtifact()
    ca.setIndentString("    ")

    ca.add("# This file is generated by xmidimapobsgen.py")
    ca.add("")
    ca.add("import traceback")
    ca.add("import coral")
    ca.add("from coral.modeler.diagram.degen import DiagramElementGenerator as DEGEN")
    ca.add("from coral.log import getLogger")
    ## This adds a few seconds to generation time, guess it's worth it?
    ca.add("logger=getLogger(\"coral.addins."+peekForLangName(inputmodels)+".xmidisynchronizerobserver\")")
    ca.add("")
    ca.add("class xmidisyncronizerobserver:")
    ca.indent(1)
    ca.add("def __init__(self):")
    ca.indent(1)
    ca.add("self.properties = {")
    ca.indent(1)
    LangStrings=extractProperties(inputmodels,ca)
    for lang in LangStrings:
        if lang != LangStrings[0]:
            old=sys.stdout
            sys.stdout=sys.stderr
            print "Combining different languages not allowed"
            print "Check your input files"
            sys.stdout=old
            sys.exit(1)

    ca.indent(-1)
    ca.add("}")
    ca.add("coral.getTransactionManager().addObserver(self.onModelChangedSyncXMIDI)")
    ca.add("")
    ca.indent(-1)
    ca.add("def onModelChangedSyncXMIDI(self, direc):")
    ca.indent(1)
    ca.add("logger.debug(\"Begin xmidisyncronizerobserver DIR=%d\",direc)")
    ca.add("")
    ca.add("if direc != coral.TRANSACTION_ORDINARY:")
    ca.indent(1)
    ca.add("return")
    ca.indent(-1)
    ca.add("already_done={}")
    ca.add("try:")
    ca.indent(1)
    ca.add("tm=coral.getTransactionManager()")
    ca.add("tr=tm.getCurrentTransaction()")
    ca.add("i= 0")
    ca.add("while i<tr.size():")
    ca.indent(1)
    ca.add("c=tr[i]")
    ca.add("i=i+1")

    ca.add("if not isinstance(c,coral.ElementChangeSlotCommand):")
    ca.indent(1)
    ca.add("continue")
    ca.indent(-1)
    ca.add("p = c.getSlot().getProperty()")
    ca.add("if not self.properties.has_key(p):")
    ca.indent(1)
    ca.add("continue")
    ca.indent(-1)

    ca.add("old_am = c.getSlot()")
    ca.add("new_am = getattr(c.getSlot().getElement(), p.getName())")
    ca.add("element = c.getSlot().getElement()")
    ca.add("property = old_am.getProperty()")
    ca.add("if already_done.has_key((element, property)):")
    ca.indent(1)
    ca.add("continue")
    ca.indent(-1)
    ca.add("already_done[(element, property)] = True")
    ca.add("ext=DEGEN.getExtension(element.__type__())")
    ca.add("if ext:")
    ca.indent(1)
    ca.add("logstr =  \"synchronize \" + str(element)")
    ca.add("logger.debug(logstr)")
    ca.add("ext.synchronizeContents(element, p.getName(), old_am, new_am)")
    ca.indent(-3)
    ca.add("except Exception, e:")
    ca.indent(1)
    ca.add("traceback.print_exc()")
    ca.add("raise e")
    ca.indent(-1)
    ca.add("logger.debug(\"End xmidisynchronizerobserver\")")
    
    ## Save output to specified file
    if output:
        ca.dump(output)
    else:
        ca.dump_fd(sys.stdout)


if __name__=='__main__':
    import sys
    main(sys.argv[1:])
