#!/usr/bin/env python
#
# This file is part of the Coral project.
# Please read the copyright notice in the LICENSE file.
#
# xmidisyncobsgen.py - Generates an observer that listens to properties specified in UML2XMIDI models
#
# SYNOPSIS
#
# xmidisyncobsgen [-o outputfile] models
#
#
# DESCRIPTION
#
# models: One or more UML 1.4 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 uml2xmidigen
# 
# 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
UML14 = coral.lang.UML14

def __otherEnd(associationend):
    association = associationend.association
    idx = 0
    if association.connection[0] == associationend:
        idx = 1
    return association.connection[idx]

def gen_property_strings(element,ca):
    ### assert len(element.clientDependency.supplier) == 1
    properties=[]
    diagram_dep = element.clientDependency.select(lambda x: x.name == "DiagramDependency")
    if diagram_dep:
        for tv in diagram_dep.taggedValue:
            m = re.search("property:\s*(.*)", str(tv.dataValue))
            if m:
                property = str(m.group(1))
                if property:
                    properties.append(property)
                    
    top_elements = [i for i in element.clientDependency.supplier]
    for top in top_elements:

        # Get the language
        m=re.search("(\w+)(\d)(\d)::\s*(.*)",str(element.name))
        assert m
        lang = m.group(1)
        ver = m.group(2)
        rev = m.group(3)
        #    L = coral.Language.__getLanguage__(lang, int(ver), int(rev))
        classname=m.group(4)

        

        association_ends = [i for i in top.association if i.aggregation == "composite"]

        ## This enables the generator to find index-properties in the models
        if str(top.name) == "GraphEdge":
            for tv in top.taggedValue:
                m = re.search("index1:\s*(.*)\s", str(tv.dataValue))
                n = re.search("index2:\s*(.*)\s", str(tv.dataValue))
                if m:
                    m2 = re.search("\[\s*element\.(.*)\s*\,\s*element\.(.*)\]", str(m.group(1)))
                    if m2:
                        properties.append(str(m2.group(1)))
                        properties.append(str(m2.group(2)))
                    else:
                        m3 = re.search("element\.(.*)", str(m.group(1)))
                        if m3 and n:
                            n2 = re.search("element\.(.*)", str(n.group(1)))
                            if n2:
                                properties.append(str(m3.group(1)))
                                properties.append(str(n2.group(1)))

        ## This generates the properties that has a special listening                        
        for attr in top.feature:
            if attr.name=="synchronizeEnds":
                synchronizeEnds=True
                if attr.initialValue:
                    properties.append(str(attr.initialValue.body))


        properties += recurse(association_ends)

    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("' + property + '"): 1,')
    if properties:
        ca.add("")
    return lang+ver+rev # UML14 for example

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

    properties={}
    to_recurse=[]
    for ae in association_ends:
        association = ae.association
        to_recurse+=[__otherEnd(ae).participant]
        
        for tv in association.taggedValue:
            m = re.search("property:\s*(.*)", str(tv.dataValue))
            if m:
                property = str(m.group(1))
                if property:
                    properties[property]=True

    next_level=[]
    for i in to_recurse:
        next_level+=[end for end in i.association if end.aggregation=="composite" and end not in association_ends]
            
    return properties.keys()+recurse(next_level)
    
def extractProperties(filenames,ca):

    langStrings=[]
    for filename in filenames:
        st=coral.loadDocument(filename)
        model = st.contents().select(lambda x: x.__type__().__language__() == UML14)[0]
        umlclasses = model.__getAllParts__().select(lambda x: x.__type__() == UML14.Class
                                                    and len(x.clientDependency)
                                                    and len(x.taggedValue) == 1
                                                    and re.search("^init\s*$", str(x.taggedValue[0].dataValue)))
        
        for umlclass in umlclasses:
            langStr=gen_property_strings(umlclass,ca)
            langStrings.append(langStr)

    return langStrings

def peekForLangName(filenames):
    if filenames:
        st=coral.loadDocument(filenames[0])
        model = st.contents().select(lambda x: x.__type__().__language__() == UML14)[0]
        umlclasses = model.__getAllParts__().select(lambda x: x.__type__() == UML14.Class
                                                    and len(x.clientDependency)
                                                    and len(x.taggedValue) == 1
                                                    and re.search("^init\s*$", str(x.taggedValue[0].dataValue)))
        
        for umlclass in umlclasses:
            m=re.search("(\w+)(\d)(\d)::\s*(.*)",str(umlclass.name))
            assert m
            lang = m.group(1)
            ver = m.group(2)
            rev = m.group(3)
            langStr=lang+ver+rev
            return langStr

def usage():
    print "Usage: xmidisyncobsgen 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 xmidisyncobsgen.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:])
