PyEmofUC

Proyecto ejemplo CountyCity

J.M. Drake, P. López Martínez y C. Cuevas

Grupo de Ingeniería Software y Tiempo Real (ISTR) - Universidad de Cantabria

Tabla de Contenidos

Proyecto CountyCity

CountyCity es un proyecto sencillo que se utiliza como ejemplo para mostrar la operatividad del entorno PyEmofUC.

El proyecto CountyCity va incluido como ejemplo en la descarga del entorno PyEmofUC. Esta compuesto por un conjunto de metamodelos y modelos organizados de acuerdo con la siguientes estructura de carpetas y ficheros a partir de la carpeta PyEmofUC:

PyEmofUC => Carpeta raíz que resulta al desempaquetar el fichero PyEmofUC_0_0.zip. Es el origen de los path relativos. 
CountyCity => Carpeta raíz del ejemplo CountyCity que se utiliza para mostrar las capacidades de PyEmofUC.

DumpedFiles => Carpeta contenedora de los ficheros que se generan en la demostración. Su contenido puede ser eliminado.
Metamodels => Carpeta contenedora de ,os metamodelos (M2) del ejemplo.
City.xmi => Formulación XMI del metamodelo City.
City.xsd => Schema para asistir la introducción de modelos conformes al metamodelo City.
County.xmi => Formulación XMI del metamodelo County.
County.xsd => Schema para asistir la introducción de modelos conformes al metamodelo County.
TaxCensus.xmi => Formulación XMI del metamodelo TaxCensus.
TaxCensus.xsd => Schema para asistir la introducción de modelos conformes al metamodelo TaxCensus.
Models => Carpeta contenedora de los modelos (M1) del ejemplo.
eagleCity.xmi =>
Ejemplo de instancia de un modelo conforme al metamodelo City.
eagleCounty.xmi => Ejemplo de instancia de un modelo conforme al metamodelo County.
Scripts => Carpeta contenedora de los scripts ejecutables del ejemplo.
CountyCity_CountyToTaxCensus_Declarative_Script.py =>
Script de demostración de una transformación M2M con estilo declarativo.
CountyCity_CountyToTaxCensus_Imperative_Script.py => Script de demostración de una transformación M2M con estilo imperativo.
CountyCity_LoadDump_Script.py => Script que muestra un proceso de carga/procesamiento/serialización de un modelo.
schema =>
Carpeta contendora de W3C-schemas de propósito general requeridos por el entorno PyEmofUC para tareas auxiliares.

EMOF.xsd => Schema del metametamodelo EmofUC. Referenciado para validación XML por las formulaciones XMI de los metamodelos.
XMI.xsd => Schema de especificación de la extensión XMI de OMG. Es importado por las formulaciones XMI de los metamodelos.
src => Carpeta raíz del código Python del entorno PyEmofUC. Su path es incluido en la variable de entorno PYTHONPATH.
 
El proyecto incluye:

Metamodelos

Metamodelo County: Contiene los datos de los hogares (Home) y colegios (School) de un barrio (County). Un hogar está compuesto por personas(member), algunos de los cuales son adultos (Adult) y otros niños (Infant). Los adultos opcionalmente tienen uno o varios trabajos (job) cuya procedencia y salario de definen en un modelo conforme al metamodelo City, y algunos de ellos tienen asignados el papel de padres (parent). Los niños (Infant) tienen asignados colegio (school). El metamodelo County también contiene información de los colegios del barrio (School) caracterizados por su nombre (name).



La descripción del metamodelo County se muestra en su formato XMI (County.xmi), formato de texto plano de dominio (County.txt) y forma plano enriquecido con color (County.rtf). Cuando se salva el metamodelo County, se generan  el esquema (County.xsd) (TODO) que define los ficheros XMI y la gramática BNF (County.bnf) (TODO) de los modelos RTF de las instancias conformes al metamodelo.

Metamodelo City: Contiene los datos de las empresas (Company) de la zona. Cada empresa ofrece un conjunto (employ) de puestos de trabajo. Cada  trabajo (Employ), está opcionalmente asignados a un trabajador (worker). que son Adultos (Adult) definidos en en un modelo que es conforme al metamodelo County.

La descripción del metamodelo City se muestra en su formato XMI (City.xmi), formato textual plano de dominio (City.Txt) y formato enriquecido (City.rtf).

Metamodelo TaxCesus: Contiene la deuda de impuestos de los adultos de un barrio cuyos datos se encuentran en un modelo conforme al metamodelo County. Contiene el nombre del barrio (countyName) el censo de los deudores del barrio (CountyCesusTax) que contiene la lista (taxPayer) de los adultos del barrio que son potenciales deudores (TaxPlayer). Cada deudor tiene asignado su nombre (payerName), su dirección (payerAddress), la referencia al elemento Adult del fichero conforme a County que corresponde, y opcionalmente  la deuda de impuestos (currentTax). La deuda (Tax) es un elemento que contiene la cantidad debida (amount) y la fecha de la evaluación (date):
La descripción del metamodelo TaxCensus se muestra en su formato XMI (TaxCensus.xmi), en el formato textual plano de dominio (TaxCensus.txt) y en el formato textual enriquecido (TaxCensus.rtf).


Modelos

Los ficheros eagleCounty.xmi, eagleCounormatosty.txt y eagleCounty.rtf, contienen el modelo eagleCounties conformes al metamodelo County formulados en los diferentes formatos formalizados en PyEmofUC. De igual modo, los ficheros eagleCity.xmi, eagleCity.txt y eagleCity.rtf, contiene el modelo eagleCity en sus diferentes formatos.

Procesamiento de modelos

El script LoadDump es un ejemplo simple de procesamiento de la información contenida en modelo utilizando el entorno PyEmofUc. El script toma como entrada el modelo eagleCounty:County con un formato XMI, y realiza la siguiente acciones sobre él:
En la siguiente tabla se muestra el código Python del script.

import pyEmofUC.resource as resource

import pyEmofUC.xmlcore as xmlcore

import pyEmofUC.GLOBAL as GLOBAL

 

xmlcore.PLATFORM = 'C:\CountyCityWorkspace'

 

# Set VERBOSE MODE

GLOBAL.IS_VERBOSE = True

 

# CountyCity metamodel URIs

countyURI = "http://unican.es/istr/PyEmofUC/CountyCity/County"

cityURI = "http://unican.es/istr/PyEmofUC/CountyCity/Cyty"

 

# The application repository is built

aRep = resource.AppRepository()

 

# Loads the EagleCity model

eagleCounty_URL = "platform:CountyCity/models/EagleCounty.xmi"

eagleCounty = aRep.loadModel(eagleCounty_URL)

 

# Example of model managing: Displays the loaded models

print '\n' + 'PRINTS LIST OF LOADED MODELS:'

for loadedModelURI in aRep.mdlRepositories.keys():

    print 5 * ' ' + loadedModelURI

   

# Example of model processing: List the name of adults without job

print '\n' + 'PRINT LIST OF ADULTS WITHOUT JOB:'

adultWithoutJob = (adult for home in eagleCounty.rootPackage.home

                             for adult in home.member

                                 if adult.isInstanceOf('cnty.Adult') and not adult.job)

for theAdult in adultWithoutJob: print 5 * ' ' + theAdult.name

 

# Dumps eagleCounty model with RTF format

dumpedEagleCounty_URL = 'platform:CountyCity/DumpedFiles/EagleCounty.rtf' 

eagleCounty.dump(dumpedEagleCounty_URL)

 

# Delete all models

del aRep

 

# eof

En la siguiente ventana se muestra el resultado que se genera en la consola cuando se ejecuta este script.


Asimismo, el script genera el fichero de texto EagleCounty.rtf en la carpeta designada por el correspondiente URL. 

Transformaciones de modelos

Transformación County_To_TaxCensus: Toma como entrada un modelo conforme al metamodelo County, y genera como salida un nuevo modelo del tipo TaxCensus. Como el fichero de entrada referencia un modelo conforme al metamodelo City, es implícita su carga y su disponibilidad para la transformación.



Formulación de la transformación con estilo declarativo: En la siguiente tabla se describe la transformación County_To_TaxCensus con un estilo declarativo. Como pseudocódigo se utiliza una formalización estilo ATL (la elección de la especificación ATL es sólo como forma de documentación, ya que éste lenguaje no es soportado por el entorno PyEmofUC).

# @nsURI county_MM=http://unican.es/istr/PyEmofUC/CountyCity/County

# @nsURI taxCensus_MM= http://unican.es/istr/PyEmofUC/CountyCity/TaxCensus

 

module County_To_TaxCensus;

create outTaxCensusModel : taxCensus_MM from inCountyModel : county_MM;

 

helper def : rootContainer() : county_MM!County = . . .;

 

helper def : currentDate() : String = . . .;

 

helper def : accumulatedSalary(theAdult: county_MM!Adult) : Real= . . .;

 

lazy rule createTax {

      from

             theAdult : county_MM!Adult

      to

             theTax : TaxCensus!Tax (

                   amount <- (0.2 * accumulatedSalary(theAdult)) –

                                                         (100.0 * len(theAdult.offspring)),

                   date <- thisModule.currentDate()

             )

}

 

entrypoint rule County_To_TaxCensus () {

      to

             theCountyTaxCensus : taxCensus_MM!CountyTaxCensus(

                   countyName <- thisModule.rootContainer().countyName,

                   taxPayer <- thisModule.rootContainer().home ->

             collect(theHome | theHome.member ->

                   select(theMember | theMember.isInstanceOf(County_MM!Adult))

             )

             )

}

 

rule Adult_To_TaxPayer {

      from

             theAdult : county_MM!Adult

      to

             theTaxPayer : taxCensus_MM!TaxPayer (

                   payerName <- theAdult.name,

                   address <- theAdult.home.address+ ’ ‘ +

                                      thisModule.rootContainer().countyAddress,

                   currentTax <- if not theAdult.job -> isEmpty()

                                      then thisModule.createTax(theAdult),

                                      else OclUndefined endif,

                   holder <- theAdult

}

 

County_MM reprenta el URI del metamodelo County

taxCensus_MM representa el URI del mm TaxCensus

 

El modulo County_To_TaxCensus representa la generación del modelo outTaxCensusModel a partir de del modelo inCountyModel

Retorna el elemeto contenedor raiz del modelo de entrada.

Retorna la fecha del  momento que se invoca.

 

Retona el salatio acumulado de un eleemnto Adult como la suma de los salarios de los job que tiene asignados.

Regla invocable que genera un elemento de tipo Tax en el modelo de salida en base a los datos de un element Adult del fichero de entrada

 

 

 

 

 

 

 

 

Regla inicial que genera un element contenedor raíz del modelo de salida en base al modelo de entrada. 

 

 

 

 

 

 

 

 

 

 

Regla que genera un element TaxPayer en el modelo de salida or cada element Adult del modelo de entrada.


En el fichero County_To_TaxCensus.atl se formula el modelo de la transformación formulada conforme al metamodelo PyEmof_UC_TL.xmi, que es una versión modificada del metamodelo QVTRelations.xmide OMG. Transformaciones formuladas conformes a este metamodelo serán soportadas por el entorno PyEmofUC en el futuro (TODO).

El fichero County_To_TaxCensus_Declarative.py contiene el código Python que implementa la transformación de acuerdo a su formulación declarativa.


Código Python

Código ATL

import emofUC.resource as resource

import emofUC.xmlcore as xmlcore

import emofUC.GLOBAL as GLOBAL

 

aRep = resource.AppRepository()

xmlcore.PLATFORM = "C:\Users\. . .\PyEMOF_UC\Workspace"

 

 

@path county_MM_URL= platform:CountyCity/County.xmi

@ path taxCensus_MM_URL= platform:CountyCity/TaxCensus.xmi

 

county_MM_URL = 'platform:CountyCity/County.xmi'

taxCensus_MM_URL = 'platform:CountyCity/TaxCensus.xmi'

aRep.loadModel(taxCensus_MM_URL)

 

 

module CountyCity_To_TaxCensus;

create outTaxCensusModel : taxCensus_MM from inCountyModel : county_MM;

outTaxCensusModelURI = "http://unican.es/istr/PyEmofUC/CountyCity/EagleTaxCensus"

outTaxCensusModelNsPrefix = 'etxc'

outTaxCensusModel = aRep.createMdlRepository(uri=outTaxCensusModelURI, mmUrl=taxCensus_MM_URL, nsPrefix=outTaxCensusModelNsPrefix)

inCountyModelURL = "platform:CountyCity/EagleCounty.xmi"

inCountyModel = aRep.loadModel(inCountyModelURL)

 

 

helper def : rootContainer() : county_MM!County = . . .;

def rootContainer(): return inCountyModel.rootPackage

 

 

helper def : currentDate() : String = . . .;

def currentDate(): return GLOBAL.currentDate()

 

 

helper def : accumulatedSalary(theAdult: county_MM!Adult) : Real= . . .;

def accumulatedSalary(theAdult): return reduce(lambda x, y:x + y, [job.salary for job in theAdult.job])

 

 

lazy rule createTax {

      from

            theAdult : county_MM!Adult

      to

             theTax : TaxCensus!Tax (

                   amount <- (0.2 * accumulatedSalary(theAdult)) –  (100.0 * len(theAdult.offspring)),

                   date <- thisModule.currentDate()

             )

}

def createTax(theAdult):

    if len(theAdult.job) > 0:

        theTax = outTaxCensusModel.createElement('txc.Tax', theAdult.xmi_id.replace('cnty', 'etxc') + '.theTax')

        theTax.amount = (0.2 * accumulatedSalary(theAdult)) - (100.0 * len(theAdult.offspring))

        theTax.date = currentDate()

        return theTax

    else: return None

 

 

entrypoint rule County_To_TaxCensus () {

      to theCountyTaxCensus : taxCensus_MM!CountyTaxCensus(

             countyName <- thisModule.rootContainer().countyName,

             taxPayer <- thisModule.rootContainer().home ->

      collect(theHome | theHome.member ->

             select(theMember | theMember.isInstanceOf(County_MM!Adult))

      )

          )

}

theCounty = rootContainer()

theCountyTaxCensus = outTaxCensusModel.createElement('txc.CountyTaxCensus', rootContainer().xmi_id.replace('cnty', 'etxc'))

theCountyTaxCensus.countyName = rootContainer().countyName

theCountyTaxCensus.taxPayer = [theMember.xmi_id.replace('cnty', 'etxc') + '.theTaxPayer' for theHome in rootContainer().home for theMember intheHome.member if theMember.isInstanceOf('cnty.Adult')]

 

 

rule Adult_To_TaxPayer {

      from theAdult : county_MM!Adult

      to theTaxPayer : taxCensus_MM!TaxPayer (

             payerName <- theAdult.name,

             address <- theAdult.home.address+ ’ ‘ + thisModule.rootContainer().countyAddress,

             currentTax <- thisModule.createTax(theAdult),

             holder <- theAdult

}

for theAdult in (theAdult for theAdult in inCountyModel.getElements() if theAdult.isInstanceOf('cnty.Adult')):

    theTaxPayer = outTaxCensusModel.createElement('txc.TaxPayer', theAdult.xmi_id.replace('cnty', 'etxc') + '.theTaxPayer')

    theTaxPayer.payerName = theAdult.name

    theTaxPayer.address = theAdult.home.address + ' ' + inCountyModel.rootPackage.countyAddress

    theTaxPayer.holder = theAdult

    theTaxPayer.currentTax = createTax(theAdult)

 

 

#Actualización de las referencias entre los objetos creados en el modelo de salida 

outTaxCensusModel.rootPackage = theCountyTaxCensus

theCountyTaxCensus.taxPayer =

       [outTaxCensusModel.getElement(xmiid) for xmiid in theCountyTaxCensus.taxPayer]

 


El resultado de la transformación puede verse en el fichero EagleTaxCensus_Declarative.xmi.



Formulación de la transformación con estilo imperativo.

En la siguiente tabla se describe la transformación County_To_TaxCensus con un estilo imperativo. El algoritmo de transformación se formula mediante un pseudocódigo basado en el recorrido de los elementos del modelo de entrada y en la creación y agregación de los correspondientes elementos del modelo en base a condicionales que evalúan las condiciones de guarda, y evaluación de atributos.

1.      Crea un repositorio de aplicación para dar soporte a la transformación de modelos.

2.       Abre el fichero de entrada y lo carga en el repositorio de la aplicación.

3.      Carga el metamodelo del modelo de salida

4.      Crea un modelo de salida vacío

5.      Crea el elemento contenedor raíz del modelo de salida.

5.1.       Asigna valor a los atributos

5.2.       FOR cada elemento Adult del modelo de entrada crea un TaxPayer en el modelo de salida.

5.2.1.  Asigna valor a los atributos del elemento TaxPayer creado.

5.2.2.  IF  el elemento Adult tiene trabajos referenciados en el campo job:

5.2.2.1.  Crea un elemento Tax y asigna los valores a los atributos.

5.2.2.2.     Agrega el elemento creado (o None si no se creado) al elemento TaxPayer



El fichero County_To_TaxCensus_Imperative.py contiene el código Python que implementa la transformación de acuerdo a su formulación imperativa.

Código Python

Seudocódigo

 

1.     Crea un repositorio de aplicación para dar soporte a la transformación de modelos.

import emofUC.resource as resource

import emofUC.xmlcore as xmlcore

import emofUC.GLOBAL as GLOBAL

 

aRep = resource.AppRepository()

xmlcore.PLATFORM = "C:\Users\. . .\PyEMOF_UC\Workspace"

 

 

2.    Abre el fichero de entrada y lo carga en el repositorio de la aplicación.

inCountyModelURL = "platform:CountyCity/EagleCounty.xmi"

inCountyModel = aRep.loadModel(inCountyModelURL)

 

 

3.    Carga el metamodelo del modelo de salida

taxCensus_MM_URL = 'platform:CountyCity/TaxCensus.xmi'

 

 

4.    Crea un modelo de salida vacío

aRep.loadModel(taxCensus_MM_URL)

outTaxCensusModelURI = "http://unican.es/istr/PyEmofUC/CountyCity/EagleTaxCensus"

outTaxCensusModelNsPrefix = 'etxc'

outTaxCensusModel = aRep.createMdlRepository(

                uri=outTaxCensusModelURI,

                mmUrl=taxCensus_MM_URL,

                nsPrefix=outTaxCensusModelNsPrefix)

 

 

5.   Crea el elemento contenedor raíz del modelo de salida.

rootContainer_xmiid = 'etxc.Id_' + str(outTaxCensusModel.nextXmiId())

theCountyTaxCensus = outTaxCensusModel.createElement(

                metaclass='txc.CountyTaxCensus',

                xmi_id=rootContainer_xmiid)

outTaxCensusModel.rootPackage = theCountyTaxCensus

 

 

5.1 Asigna valor a los atributos

theCountyTaxCensus.countyName = inCountyModel.rootPackage.countyName

 

 

5.2  FOR cada elemento Adult del modelo de entrada crea un TaxPayer en el modelo de salida

for theAdult in (theAdult for theAdult in inCountyModel.getElements() if theAdult.isInstanceOf('cnty.Adult')):

 

 

5.2.1. Asigna valor a los atributos del elemento TaxPayer creado

    theTaxPayer = outTaxCensusModel.createElement('txc.TaxPayer', 'etxc.Id_' +

                                                                                                            str(outTaxCensusModel.nextXmiId()))

    theTaxPayer.payerName = theAdult.name

    theTaxPayer.address = theAdult.home.address + ' ' + inCountyModel.rootPackage.countyAddress

    theTaxPayer.holder = theAdult

 

 

5.2.2.  IF  el elemento Adult tiene trabajos referenciados en el campo job:

    theTax = None

    if len(theAdult.job) > 0:

 

 

 

5.2.2.1.  Crea un elemento Tax y asigna los valores a los atributos.

        theTax = outTaxCensusModel.createElement('txc.Tax', 'etxc.Id_' +

                                                                                                         str(outTaxCensusModel.nextXmiId()))

        theTax.amount = -100.0 * len(theAdult.offspring)

        for theJob in theAdult.job:

            theTax.amount += 0.2 * theJob.salary

            theTax.date = GLOBAL.currentDate()

 

 

  5.2.2.2.      Agrega el elemento creado (o None si no se creado) al elemento TaxPayer

    theTaxPayer.currentTax = theTax

    theCountyTaxCensus.taxPayer.append(theTaxPayer)

 


El resultado de la transformación puede verse en el fichero EagleTaxCensus_Imperative.rtf.

Documentos

Enlaces de interés