Personal tools
You are here: Home Developers Corner MetaModel and machinery ObjectDomain reading script
Document Actions

ObjectDomain reading script

Script for extracting model information from ObjectDomain. The header comments describe the special conventions for storing the model within ObjectDomain.

Click here to get the file

Size 60.4 kB - File type text/python-source

File contents

"""code for Generating in-memory CCPN MetaModel from inside ObjectDomain

Rules for ObjectDomain version of dataModel
  
 Rules for View names:
 - As the program uses the qualified names of objects to identify them,
 Views must have names that do not overlap with classes, dataTypes or packages
 contained in the same package. Currently all view names are automatically
 made to start with an underscore ('_'), but this could be replaced with a
 constraint on the names.
 
 Special rules for Associations:
 - A class may have an association with itself where only one role is desired,
  e.g. for Resonance.covalentlyBound.
  If the two roles are identical (except that one of them may have 
  'documentation' empty and the other not), this will interpreted it as a 
  symmetrical link requiring only one role, and will not throw an error. 
  This role will be its own otherRole.
 
 Default Values:
  Default values must be converted to the correct dataType before being passed
  to MetaModel classes.
  For single valued attributes the value is simply the appropriate string.
  For multivalued attributes the default value is given as a tuple of strings,
  such that e.g. a default value of two integers must be given as 
  ('0','1').
  Parameter defaults folow the same rules as attribute defaults, except that
  only parameters with hicard == 1 can have default values
  An attribute with a default value and hicard==1 will automatically get
  locard = 0.
 
 Multiplicities:
  Multiplicities are found for attributes, links, and parameters
 
  Hicard==1 elements are by definition unordered and unique.
 
  For attributes and links the cardinality defaults to 0..1.
  For parameters it defaults to 1..1
 
  Links with hicard !=1 default to  unordered, unique.
  Attributes and parameters with hicard != 1 default to ordered, nonunique.
 
  The type expression field of attributes that are not 0..1 is set by the
  program as e.g. [3], [*], [0..2].
  
  For parameters the multiplicity is set by the type expression field, if set.
  The recommended format is e.g. [0..1], [2]. [4]{+u}, [*], 1..3]{+u-o}
  See parseMultiplicity function for exact rules.
  If this field is not set, the default is 1..1, unordered, unique
 
 
 NBNB removed hasSpecialConstructor, hasSpecialDestructor
 
 Meaning of tagged values:
 - documentation (all): Documentation string for python, html documentation ...
 - keyNames (class): The attribute(s) that serve as key and identify an object 
  relative to its parent object. NBNB change 
 - baseName(role,attribute): Role or attribute name used to generate names
  that need a name in the singular. Mandatory for list attributes, 
  defaults to class names for -to-many links.
 - isDerived (attribute, association): Derived attributes and links are in 
  principle calculated on the fly at getting time (the implementation may
  precalculate them). Derived elements can not be set or removed (unless 
  specially coded to make  it possible),  Derived elements are not stored to 
  file. There is no entry in object.__dict__ for a derived attrribute.
 - isImplementation (attribute, association): Implementation attributes and 
  links are are set or derived by the implementation. The code generation
  machinery will provide the necessary code. They must be frozen and have 
  hicard ==1  Implementation elements are not stored to file. 
 - isAutomatic (attribute, association): Automatic attributes and links can 
  not be set, and the setting of them must be hardcoded in the API. They are 
  stored normally and cannot be derived. Typically serials or timestamps
 - code:python (etc.) (operation). Python code that makes up the complete body of an 
  operation. The code is correct Python, written flush left with two-space
  indentation. Imported directly into the Python API, and serves as
  documentation for other languages.
  Similarly for other languages
 - throws (operation). A comma-separated list of qualified names of operations
  that are thrown by the operation. For operations that are otherwise
  autogenerated (i.e. all opTypes that do not start with 'other') the standard
  exceptions are added automatically, so you only need to specify non-standard
  ones.
 - ConstructorCode:python (etc.) (class). Code to add to the autogenerated
  class constructor (__init__). Intended to handle creation of mandatory child 
  objects. The code is incorporated after the object has been created, but 
  before any validity checks, and is not executed if reading==True. 
  Syntax as for code:python.
  Similarly for other languages
 - DestructorCode:python (etc.) (class). Code to add to the autogenerated
  class destructor (delete). Intended to check whether the object can be deleted.
  The code is incorporated at the very top of the function, before any actions
  have been carried out. 
  Syntax as for code:python.
 - isOpen (dataType): Boolean determining whether an enumeration is open.
 - enumeration (dataType): list of permitted values for an enumeration.
  The enumeration is given as a tuple of python strings (complete with quotation
  marks), whatever the actual type of the enumeration, in the same way as 
  default values are given.
 - length (dataType): maximum length of a string dataType.
 - isOrdered (Attribute) Is order of atributes significant?
   Only meaningful if hicard != 1.
   Only used where it differs from the default
 - isUnique (Attribute, Role) Are duplicates allowed?
   Only meaningful if hicard != 1.
   Only used where it differs from the default
 
 Constraints:
 Objectdomain does not allow tagged values in constraints. We need them, 
 however, for handling code for various languages. The constraint body is 
 therefore interpreted as follows:
 Lines consisting of a single underscore are separators
 The string is of tag, separator, value, separator, ...; 
 
 
 Constants:
 MetaConstants are represented in OD by a class with stereotype 'Constant'.
 There must be no links and a single attribute named 'value'.
 The value and type of the constant is detemined by the defaultValue and
 type of the 'value' attribute.
 
 Exceptions:
 MetaConstants are represented in OD by a class with stereotype 'Exception'.
 They can have no attributes, links or operations.
 
 
 DataTypes:
 DataTypes are represented in OD by a class with stereotype 'DataType'.
 They can have no attributes, links or operations.
 
 DataObjTypes:
 DataObjTypes are represented in OD by a class with stereotype 'DataType', like
 DataTypes. They must have atttibutes, may have operations and may not have 
 links. They are handled like Classes. Only exceptions: 
 They lack isSingleton, destructorCodeStubs, and keyNames

======================COPYRIGHT/LICENSE START==========================

ObjectDomain.py: Code generation for CCPN framework

Copyright (C) 2005 Rasmus Fogh (CCPN Project)

=======================================================================

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
 
A copy of this license can be found in ../../../license/GPL.license
 
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
 
You should have received a copy of the GNU General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA


======================COPYRIGHT/LICENSE END============================

for further information, please contact :

- CCPN website (http://www.ccpn.ac.uk/)

- email: ccpn@bioc.cam.ac.uk

=======================================================================

If you are using this software for academic purposes, we suggest
quoting the following references:

===========================REFERENCE START=============================
R. Fogh, J. Ionides, E. Ulrich, W. Boucher, W. Vranken, J.P. Linge, M.
Habeck, W. Rieping, T.N. Bhat, J. Westbrook, K. Henrick, G. Gilliland,
H. Berman, J. Thornton, M. Nilges, J. Markley and E. Laue (2002). The
CCPN project: An interim report on a data model for the NMR community
(Progress report). Nature Struct. Biol. 9, 416-418.

Rasmus H. Fogh, Wayne Boucher, Wim F. Vranken, Anne
Pajon, Tim J. Stevens, T.N. Bhat, John Westbrook, John M.C. Ionides and
Ernest D. Laue (2005). A framework for scientific data modeling and automated
software development. Bioinformatics 21, 1678-1684.

===========================REFERENCE END===============================
"""

# NBNB TBD renamed mainkey to keyNames TBD
# NBNB TBD consider reorganising codestubs etc.
#          to allow e.g. python:file:ME tags

# NBNB HACK used for renaming of old to new tagged values
renameTags = {
 'mainkey':'keyNames',
 'pythonConstructorCode':'constructorCode:python',
 'javaConstructorCode':'constructorCode:java',
 'pythonDestructorCode':'destructorCode:python',
 'javaDestructorCode':'destructorCode:java',
 'pythonCode':'code:python',
 'javaCode':'code:java',
 'typeCode':'typeCode:python',
 'javaTypeCode':'typeCode:java',
 'xmlTypeCode':'typeCode:xml',
 'xmlCode':'code:xml',
}

skipTags = [
 'hasSpecialConstructor', 'hasSpecialDestructor', 'altClassName',
 'jdbcTypeCode', 'javaSimpleTypeCode', 'BMRBstatus', 'getByKey',
 '',
]

# general imports
import string
import time

# NBNB ObjectDomain always works under Python 2.1
# Ccp imports
from memops.universal.Constants import True, False, trueString, falseString

# Ccp imports
import memops.general.Constants as genConstants
baseDataTypeModule = genConstants.baseDataTypeModule

from memops.metamodel import ImpConstants

from memops.metamodel import MetaModel
MemopsError = MetaModel.MemopsError

from memops.metamodel import OpTypes

from memops.metamodel import TaggedValues

import memops.universal.Util as uniUtil
import memops.metamodel.Util as metaUtil

# set up guid generation
from odUserSetup import odUser
guidGenerator = metaUtil.SimpleGuidGenerator(odUser.operator,
                                             odUser.organisation)
newGuid = guidGenerator.newGuid


OdContainingPackageName = 'Model.Logical'

# Global variables:

# qualifiedName of Od package containing the topmost Ccp package
OdDataRootClassName = '.'.join([OdContainingPackageName,
                                ImpConstants.modellingPackageName,
                                ImpConstants.implementationPackageName,
                                ImpConstants.dataRootName])
OdDataTypeObjectClassName = '.'.join([OdContainingPackageName,
                                ImpConstants.modellingPackageName,
                                ImpConstants.implementationPackageName,
                                ImpConstants.baseDataTypeObjName])

# allowedTags gives the tagged values allowed for each category, 
# and the enumerated values (if any)
# NB These are tagged values used in ObjectDomain but stored differently 
# in the MetaModel. 
# Tagged values stored as such in the MetaModel are handled in MetaModel.py
#
# allowedTags that end in a colon (':') must match only the start of the
# actual tag. I.e. an allowedTag 'code:' would match 'code:python'.

allowedTags = {
 'MetaClass':{
  'documentation':None,
  'keyNames':None,
  'guid':None,
  'constructorCode:':None,
  'destructorCode:':None,
  'partitionsChildren':(trueString,),
 },
 'MetaAttribute':{
  'documentation':None,
  'baseName':None,
  'isDerived':(trueString,),
  'isImplementation':(trueString,),
  'isAutomatic':(trueString,),
  'isUnique':(trueString, ),
  'isOrdered':(falseString,),
  'isAbstract':(trueString,),
  'guid':None,
 },
 'MetaPackage':{
  'shortName':None,
  'documentation':None,
  'guid':None,
 },
 'MetaAssociation':{
  'isDerived':(trueString,),
  'isImplementation':(trueString,),
  'isAutomatic':(trueString,),
  'partitionsChildren':(trueString,),
 },
 'MetaRole':{
  'documentation':None,
  'baseName':None,
  'isOrdered':(trueString,),
  'isUnique':(falseString,),
  'guid':None,
 },
 'MetaOperation':{
  'documentation':None,
  'opType':tuple(OpTypes.operationData.keys()),
  'opSubType':None,
  'guid':None,
  'throws':None,
  'code:': None
 },
 'MetaParameter':{
  'documentation':None,
  'guid':None,
 },
 'MetaDataType':{
  'documentation':None,
  'typeCode:':None,
  'isOpen':(trueString,falseString),
  'enumeration':None,
  'length':None,
  'guid':None,
 },
 'MetaConstant':{
  'guid':None,
 },
 'MetaConstraint':{
  'guid':None,
 },
 'MetaException':{
  'guid':None,
  'scope':None,
 },
 'MetaDataObjType':{
  'documentation':None,
  'guid':None,
  'constructorCode:':None,
 }
}

# NBNB TBD check various TypeCodes

# Default multiplicities:
# The standard default is set in the MetaModel
                          
# Attributes with hicard != 1
defaultMultiAttrMultiplicity = {'isUnique':False, 'isOrdered':True}
                          
# Parameters with hicard != 1
defaultMultiParMultiplicity = {'isUnique':False, 'isOrdered':True}

# Roles with hicard != 1
defaultMultiRoleMultiplicity = {'isUnique':True, 'isOrdered':False}

# default value of hicard when not set explicitly
defaultHicard = 1

# OD imports:
from pawt import swing
import objectdomain.domain.DomainViewHandler as OdDomainViewHandler
import objectdomain.uml.view.BaseViewElement as OdBaseViewElement
import objectdomain.uml.mechanisms.UmlPackage as OdUmlPackage
import objectdomain.uml.core.UmlClass as OdUmlClass
import objectdomain.uml.core.UmlAssociation as OdUmlAssociation
import objectdomain.uml.core.IDependency as OdDependency
import objectdomain.uml.core.IGeneralization as OdGeneralization
import objectdomain.uml.core.IUmlDiagram as OdUmlDiagram
import objectdomain.uml.core.UmlComment as OdUmlComment
#import objectdomain.uml.core.UmlClassifier
#import objectdomain.uml.core.UmlInterface
#import objectdomain.uml.core.UmlOperation
#import objectdomain.uml.core.UmlAttribute
#import objectdomain.uml.core.UmlParameter
#import objectdomain.uml.core.UmlTaggedValue
#import objectdomain.uml.mechanisms.UmlBaseClass
#import objectdomain.uml.mechanisms.UmlComponent

import odUtil, odApp
od = odApp.OdApp()


def modelFromOd():
  """ Make in-memory CCPN metamodel.
  Must be run inside ObjectDomain
  Usage:
  1) run this function.
  Toplevel packages are those that are contained within the package named
  OdContainingPackageName (currently 'Model.Logical')
  modelFromOd will create an in-memory python model corresponding all toplevel
  packages and their contents
  """
  
  start = time.time()
  
  # create root package
  global rootPackage
  rootPackage = MetaModel.MetaPackage(name=ImpConstants.rootPackageName,
   guid='www.ccpn.ac.uk_RasmusFogh_2006-06-21-19:13:29_00000',
   taggedValues={'packageGroup':TaggedValues.defaultPackageGroup}
  )
  
  # get map of DataObjTypes ahead of time
  dataTypeObjMap = {}
  ll = [od.get(OdDataTypeObjectClassName)]
  for dto in ll:
    dataTypeObjMap[dto] = None
    ll.extend([x.getSubtype() 
               for x in odUtil.toList(dto.startSpecializations())])
    
  # process packages and make direct contents
  topContainer = odObjFromQualName(OdContainingPackageName)
  elementList = odUtil.toList(topContainer.startOwns())
  for ee in elementList:
    if isinstance(ee,OdUmlPackage):
      # recursively create and add contained package and its direct contents
      objectsFromOd(ee, rootPackage, dataTypeObjMap)
  
  # get list of leaf packages:
  packages = [rootPackage]
  leafPackages = []
  for pp in packages:
    ll = pp.containedPackages
    if ll:
      pp._MetaPackage__containedPackageNames.sort()
      packages.extend(ll)
    else:
      leafPackages.append(pp)
  
  # sort names of package contents
  for pp in leafPackages:
    for tag in ('_MetaPackage__classNames', '_MetaPackage__dataTypeNames', 
                '_MetaPackage__dataObjTypeNames', '_MetaPackage__constantNames',
                '_MetaPackage__exceptionNames',):
      getattr(pp,tag).sort()
  
  # create package connections
  for metaPackage in leafPackages:
    addPackageConnections(metaPackage)
  
  #sort import/access links
  for metaPackage in leafPackages:
    dd = metaPackage._MetaModelElement__dataDict
    dd['importedPackages'] = metaUtil.sortByMethodCall(dd['importedPackages'], 
                                                       'qualifiedName')
    dd['accessedPackages'] = metaUtil.sortByMethodCall(dd['accessedPackages'], 
                                                       'qualifiedName')
  
  # create supertype links
  # NB has to happen after package connections are ready
  for metaPackage in leafPackages:
    for ee in metaPackage.dataTypes:
      setSupertypes(ee)
    for ee in metaPackage.dataObjTypes:
      setSupertypes(ee)
    for ee in metaPackage.classes:
      setSupertypes(ee)
    for ee in metaPackage.exceptions:
      setSupertypes(ee)
  
  # get DataTypes in inheritance order
  dataTypes = []
  for metaPackage in leafPackages:
    dataTypes.extend(metaPackage.dataTypes)
  dataTypes = metaUtil.sortByInheritance(dataTypes)
  
  # copy down DataType attributes - must be done in inheritance order
  for ee in dataTypes:
    inheritToDataType(ee)
  
  # process DataType attributes (in practice: convert enumerations)
  for ee in dataTypes:
    finaliseDataType(ee)
      
    dd = ee._MetaModelElement__dataDict
    dd['subtypes'] = metaUtil.sortByAttribute(dd['subtypes'],'name')
  
  # create MetaConstants - better done after datatypes are finalised
  for metaPackage in leafPackages:
    
    for qname in metaPackage._tempData['constantNames']:
      createMetaConstant(metaPackage, qname)
    del metaPackage._tempData['constantNames']
  
  # finalise remaining objects
  for metaPackage in leafPackages:
      
    # DataObjTypes
    for ee in metaPackage.dataObjTypes:
      attributesFromOd(ee)
      operationsFromOd(ee)
      
      dd = ee._MetaModelElement__dataDict
      dd['subtypes'] = metaUtil.sortByAttribute(dd['subtypes'],'name')
    
    # classes
    partitioners = []
    for ee in metaPackage.classes:
    
      rolesFromOd(ee)
      attributesFromOd(ee)
      operationsFromOd(ee)
      
      ee._MetaClass__roleNames.sort()
      
      # sort subtypes alphabetically
      dd = ee._MetaModelElement__dataDict
      dd['subtypes'] = metaUtil.sortByAttribute(dd['subtypes'],'name')
      
      # sort supertypes. NBNB relies on the rule that only the first supertype
      # can have a supertype in turn, and that the order of the does not matter.
      ll = dd['supertypes']
      if len(ll) > 1:
        firstSupertype = None
        if not ll[0]._MetaModelElement__dataDict['supertypes']:
          for obj in ll[1:]:
            if obj._MetaModelElement__dataDict['supertypes']:
              firstSupertype = obj
              break
          if firstSupertype is not None:
            ll.remove(firstSupertype)
            ll.insert(0,firstSupertype)
      
      # handle partitionsChildren
      if ee.partitionsChildren:
        partitioners.append(ee)
    
    for ee in partitioners:
      for xx in ee.getAllSubtypes():
        xx.partitionsChildren=True
      
    # exceptions
    for ee in metaPackage.exceptions:
      dd = ee._MetaModelElement__dataDict
      dd['subtypes'] = metaUtil.sortByAttribute(dd['subtypes'],'name')
  
  # add operation targets
  for metaPackage in leafPackages:
      
    # DataObjTypes and classes
    for ee in metaPackage.dataObjTypes + metaPackage.classes:
      for op in ee.operations:
        op.target = OpTypes.getTarget(op)
  
  print ("Generated without errors: %s" 
         % rootPackage._MetaPackage__containedPackageNames)
  
  mid = time.time()
  
  rootPackage.checkValid()
  
  
  # check operator and organisation for illegal characters
  for ss in  (odUser.operator, odUser.organisation):
    if ss:
      for char in """&'"<>""":
        if char in ss:
          raise MemopsError(
           "user or operator string %s contains illegal character %s"
           % (ss,repr(char))
          )
          
  end = time.time()
  print ("Checks valid: %s - generation %.3fs, validity check %.3fs" 
   % (rootPackage._MetaPackage__containedPackageNames, (mid-start), (end-mid))
  )
  #
  return rootPackage


def getTopPackageName(odObj):
  """ get name of top-level package containing odObj
  By definition this is also the qualified name of the top-level package.
  """
  nn = len(OdContainingPackageName) + 1
  qname = odObj.qualifiedName[nn:]
  return qname.split('.')[0]
  

def objectsFromOd(odPackage, container, dataTypeObjMap):
  """  create metaPackage instance, recursively create contained packages,
  and create all objects directly contained in packages.
  """
  
  #######################################################################
  # make package
  
  name = odPackage.name
  guid = getGuid(odPackage)
    
  tagVals, extraTaggedValues = taggedValuesFromOd(odPackage, 'MetaPackage')
  
  # set packageGroup to default, if necessary
  if not extraTaggedValues.has_key('packageGroup'):
    extraTaggedValues['packageGroup'] = TaggedValues.defaultPackageGroup
  
  # get parameters
  params = {
   'name'         : name,
   'guid'         : guid,
   'container'    : container,
   'documentation': odPackage.getDocumentation() or None,
   'taggedValues' : extraTaggedValues,
   'isRoot'       : uniUtil.toBoolean(odPackage.isInheritanceRoot()),
   'isLeaf'       : uniUtil.toBoolean(odPackage.isLeaf()),
   'isAbstract'   : uniUtil.toBoolean(odPackage.isAbstract()),
   'visibility'   : str(odPackage.getVisibility()) + '_vis',
   'shortName'    : tagVals.get('shortName'),
  }
  
  # create package
  metaPackage = MetaModel.MetaPackage(**params)  
  
  ########################################################################
  # make package contents. 
  
  elementList = odUtil.toList(odPackage.startOwns())
  
  tempnames = {}
  diagnames = []
  
  # add temporary list to metaPackage
  metaPackage._tempData['constantNames'] = []
  
  for ee in elementList:
    
    # check for name overlap
    if tempnames.has_key(ee.name):
      raise MemopsError("Package namespace overlap for %s" % ee.qualifiedName)
    elif ee.name:
      tempnames[ee.name] = None
        
    if isinstance(ee,OdUmlPackage):
      # recursively create and add contained package
      
      # NBNB TBD check here if package is not loaded, and skip if it is.
      
      objectsFromOd(ee, metaPackage, dataTypeObjMap)
  
    elif isinstance(ee,OdUmlClass):
      # create class or datatype
      stereotype = ee.getStereotype().name
      
      if stereotype == 'DataType':
        if dataTypeObjMap.has_key(ee):
          # DataObjType - NB it *is* 'classFromOd', it is not a typo
          classFromOd(metaPackage,ee)
        else:
          # DataType
          dataTypeFromOd(metaPackage,ee)
      
      elif stereotype == 'Constant':
        # constant
        metaPackage._tempData['constantNames'].append(ee.qualifiedName)
      
      elif stereotype == 'Exception':
        # exception
        exceptionFromOd(metaPackage,ee)
      
      elif stereotype == 'unspecified':
        # class
        classFromOd(metaPackage,ee)
      
      else:
        raise MemopsError(' Class %s has unimplemented stereotype %s' %
         (ee.qualifiedName,stereotype)
        )
      
    elif isinstance(ee,OdDependency):
      # handle on next pass
      pass
    
    elif isinstance(ee,OdUmlDiagram):
      # add to diagram name list
      diagnames.append(ee.name)
        
    elif isinstance(ee,OdUmlComment):
      # ignore
      pass
    
    else:
      # NB we are not supporting other elements. 
      # This includes generalizations (package inheritance)
      print 'Error on element of class :', ee.__class__
      print 'Error on element of class named:', ee.__class__.__name__
      raise MemopsError(" package contains object %s  of unexpected type"
       % ee.qualifiedName
      )
  
  # sort out docDiagramNames tagged value
  if diagnames:
    diagnames.sort()
    metaPackage.addTaggedValue('docDiagramNames',' '.join(diagnames))
  
  # clean up branch packages
  if metaPackage.containedPackages:
    del metaPackage._tempData['constantNames']

  #
  return metaPackage


def classFromOd(metaPackage, odClass):
  """ create MetaClass or MetaDataObjType within metaPackage
  """
  name = odClass.name
  guid = getGuid(odClass)
  
  tagVals, extraTaggedValues = taggedValuesFromOd(odClass, 'MetaClass')
  
  # get parameters
  params = {
   'name'         : name,
   'guid'         : guid,
   'container'    : metaPackage,
   'documentation': odClass.getDocumentation() or None,
   'taggedValues' : extraTaggedValues,
   'isRoot'       : uniUtil.toBoolean(odClass.isInheritanceRoot()),
   'isLeaf'       : uniUtil.toBoolean(odClass.isLeaf()),
   'isAbstract'   : uniUtil.toBoolean(odClass.isAbstract()),
   'visibility'   : str(odClass.getVisibility()) + '_vis'
  }
  
  # set constructor code stubs
  dd = tagVals.get('constructorCode')
  if dd:
    params['constructorCodeStubs'] = dd
  
  if odClass.getStereotype().name == 'DataType':
    # create DataObjType
    
    if [x for x in odUtil.toList(odClass.startOwns()) 
        if isinstance(x,OdUmlAssociation)]:
      raise MemopsError("DataObjType %s.%s has roles" % (metaPackage, name))
    
    clazz = MetaModel.MetaDataObjType(**params)
    
  else:
    # set destructor code stubs
    dd = tagVals.get('destructorCode')
    if dd:
      params['destructorCodeStubs'] = dd
    
    if tagVals.get('partitionsChildren') == trueString:
      params['partitionsChildren'] = True
    
    # create class
    clazz = MetaModel.MetaClass(**params)
 
    # NB isSingleton is not defined in OD. Would require a tagged value.
    # use default in MetaModel
 
    # set keyNames
    keyNames = tagVals.get('keyNames','')
    if keyNames:
      for kk in map(string.strip,string.split(keyNames,',')):
        clazz.addKeyName(kk)
  
  # Constraints:
  addConstraints(clazz, odClass)
  

def dataTypeFromOd(metaPackage, odDataTypeClass):
  """ create metaDataType within metaPackage
  """
  name = odDataTypeClass.name
  guid = getGuid(odDataTypeClass)
  
  if odUtil.toList(odDataTypeClass.operationElements()):
    raise MemopsError("DataType %s.%s has operations" % (metaPackage, name))
    
  if odUtil.toList(odDataTypeClass.attributeElements()):
    raise MemopsError("DataType %s.%s has attributes" % (metaPackage, name))
  
  if [x for x in odUtil.toList(odDataTypeClass.startOwns()) 
      if isinstance(x,OdUmlAssociation)]:
    raise MemopsError("DataType %s.%s has roles" % (metaPackage, name))
  
  tagVals, extraTaggedValues = taggedValuesFromOd(odDataTypeClass,
                                                  'MetaDataType')
  
  # get parameters
  params = {
   'name'         : name,
   'guid'         : guid,
   'container'    : metaPackage,
   'documentation': odDataTypeClass.getDocumentation() or None,
   'taggedValues' : extraTaggedValues,
   'isRoot'       : uniUtil.toBoolean(odDataTypeClass.isInheritanceRoot()),
   'isLeaf'       : uniUtil.toBoolean(odDataTypeClass.isLeaf()),
   'isAbstract'   : uniUtil.toBoolean(odDataTypeClass.isAbstract()),
   'visibility'   : str(odDataTypeClass.getVisibility()) + '_vis'
  }

  # set type code stubs
  dd = tagVals.get('typeCode')
  if dd:
    params['typeCodes'] = dd
  
  isOpen = tagVals.get('isOpen')
  if isOpen == trueString:
    params['isOpen'] = True
  elif isOpen == falseString:
    params['isOpen'] = False
  
  length = tagVals.get('length')
  if length is not None:
    params['length'] = int(length)
  
  # NB The enumeration evals to a tuple of strings.
  # Is converted to the right dataType during the next pass
  enums = tagVals.get('enumeration')
  if enums is not None:
    params['enumeration'] = eval(enums,{})
  
  dataType = MetaModel.MetaDataType(**params)
  
  # Constraints:
  addConstraints(dataType,odDataTypeClass)


def exceptionFromOd(metaPackage,odClass):
  """ create MetaException within metaPackage
  """
  
  name = odClass.name
  guid = getGuid(odClass)
  
  tagVals, extraTaggedValues = taggedValuesFromOd(odClass, 'MetaException')
  
  # get parameters
  params = {
   'name'         : name,
   'guid'         : guid,
   'container'    : metaPackage,
   'documentation': odClass.getDocumentation() or None,
   'taggedValues' : extraTaggedValues,
   'visibility'   : str(odClass.getVisibility()) + '_vis',
  }
  
  # get scope
  scope = tagVals.get('scope')
  if scope:
    params['scope'] = scope
  
  exception = MetaModel.MetaException(**params)


def addPackageConnections(metaPackage):
  
  # get metaPackage from Od side
  odPackage = odObjFromQualName(metaPackage.qualifiedName())
  
  for ee in [x for x in odUtil.toList(odPackage.startOwns())
                     if isinstance(x,OdDependency)]:
    
    suppliers = odUtil.toList(ee.startSuppliers())
    clients = odUtil.toList(ee.startClients())
 
    if len(suppliers) != 1:
      for xx in suppliers:
        print "Supplier %s" % xx.qualifiedName
      raise MemopsError("Dependency %s must have a single supplier, has %s"
       % (ee.qualifiedName, len(suppliers))
      )
 
    elif  len(clients) != 1:
      for xx in clients:
        print "Client %s" % xx.qualifiedName
      raise MemopsError("Dependency %s must have a single client, has %s"
       % (ee.qualifiedName, len(clients))
      )
 
    if isinstance(clients[0],OdUmlComment):
      pass
 
    elif ee.getStereotype().name == 'import':
      # process package import
 
      ll = odUtil.toList(ee.startSuppliers())
      metaPackage.addImportedPackage(metaObjFromQualName(ll[0].qualifiedName))
 
    else:
      raise MemopsError(" Dependency %s is of unsupported type"
       % ee.qualifiedName
      )
  
  # Add Implementation and AccessControl package import
  implPackage = metaObjFromQualName('%s.%s' % 
   (ImpConstants.modellingPackageName, ImpConstants.implementationPackageName)
  )
  accessControlPackage = metaObjFromQualName('%s.%s' % 
   (ImpConstants.modellingPackageName, ImpConstants.accessControlPackageName)
  )
  if metaPackage is not implPackage:
  
    if implPackage not in metaPackage.importedPackages:
      metaPackage.addImportedPackage(implPackage)
      
    if metaPackage is not accessControlPackage:
      if accessControlPackage not in metaPackage.importedPackages:
        metaPackage.addImportedPackage(accessControlPackage)


def setSupertypes(metaObj):
  """ set superType link (if any)
  """ 
  
  odObj = odObjFromQualName(metaObj.qualifiedName())
  
  elementList = odUtil.toList(odObj.startOwns())
  generalisations = [ee for ee in odUtil.toList(odObj.startOwns())
                      if isinstance(ee, OdGeneralization)]
             
  if generalisations:    
    supertypes = [metaObjFromQualName(gg.getSupertype().qualifiedName)
                  for gg in generalisations]     
    
    metaObj.supertypes = supertypes  


def inheritToDataType(subType):
  """ copy attributes down from supertype to subtype
  NB in ObjectDomain these attributes are not set if they are the same
  as those in the superType
  NB the function assumes that inheritance is done in the supertype
  before it is done in the subtype.
  """
  
  superType = subType.supertype
  
  if superType is not None:
    
    subTypeCodes = subType.typeCodes
    for tag,val in superType.typeCodes.items():
      if not subTypeCodes.has_key(tag):
        subType.addTypeCode(tag,val)
  
    if subType.length is None:
      subType.length = superType.length
  
    if not subType.enumeration:
      subType.enumeration = superType.enumeration
  
    if subType.isOpen is None:
      subType.isOpen = superType.isOpen
 
 
def finaliseDataType(dataType):
  """ Convert enumerations from strings to correct type
  """
  
  # convert enumerations from String (now the dataType is complete)
  enum = dataType.enumeration
  if enum:
    typeCode = dataType.typeCodes.get('python')
    if typeCode:
      func = getattr(genConstants.baseDataTypeModule,typeCode).fromString
      try:
        dataType.enumeration = tuple(map(func,enum))
      except:
        print ("%s error in converting enumeration to correct data type" 
               % dataType)
        raise
    else:
      raise MemopsError("%s has no python typeCode - may be missing superType"
       % dataType
      )


def createMetaConstant(metaPackage,qname):
  """ create MetaConstant within metaPackage
  """
  
  odClass = odObjFromQualName(qname)
  
  name = odClass.name
  guid = getGuid(odClass)
  
  tagVals, extraTaggedValues = taggedValuesFromOd(odClass, 'MetaConstant')
  
  # get parameters
  params = {
   'name'         : name,
   'guid'         : guid,
   'container'    : metaPackage,
   'documentation': odClass.getDocumentation() or None,
   'taggedValues' : extraTaggedValues,
  }
  
  # get value attribute
  for odAttr in odUtil.toList(odClass.attributeElements()):
    
    # get attribute 'value'
    valueAttr = None
    if odAttr.name == 'value':
      if valueAttr is None:
        valueAttr = odAttr
      else:
        raise MemopsError(
         "%s: MetaConstant has more than one attribute named 'value'" % (self,)
        )
    else:
      raise MemopsError(
       "%s: MetaConstant attribute named %s instead of 'value'" 
       % (self,odAttr.name)
      )
  
  # set valueType
  valueType = metaObjFromQualName(valueAttr.getTypeRef().qualifiedName)
  parameters['valueType'] = valueType
  
  # get value
  defVal = valueAttr.getInitialValue()
  if defVal:
    typeCode = valueType.typeCodes.get('python')
    if typeCode:
      func = getattr(genConstants.baseDataTypeModule,typeCode).fromString
      try:
        value = func(defVal)
      except:
        print ("%s error in converting value to correct data type"
               % qname)
        raise
      parameters['value'] = value
    else:
      raise MemopsError("%s has no python typeCode - may be missing superType"
       % valueType
      )
  else:
    raise MemopsError("%s.value hs no default value" % qname)
  
  constraint = MetaModel.MetaConstraint(**params)
    

def attributesFromOd(metaObj): 
  """ get all attributes from odClass and put them in appropriate metaClass
  NB requires that dataTypes are already read and postprocessed.
  metaObj may be either a MetaClass or a MetaDataObjType
  """
  odClass = odObjFromQualName(metaObj.qualifiedName())
  
  for odAttr in odUtil.toList(odClass.attributeElements()):
  
    name = odAttr.name
    guid = getGuid(odAttr)
    
    if not name:
      raise MemopsError("nameless attribute in %s" % metaObj)
    
    try:
      valueType = metaObjFromQualName(odAttr.getTypeRef().qualifiedName)
    except:
      print ("Error setting type of %s.%s to %s" %
       (odClass.qualifiedName, name, `odAttr.getTypeRef()`)
      )
      raise
    
    tagVals, extraTaggedValues = taggedValuesFromOd(odAttr, 'MetaAttribute')
    
    documentation = odAttr.getDocumentation() or None
    params = {
     'container'    : metaObj,
     'name'         : name,
     'guid'         : guid,
     'documentation': documentation,
     'taggedValues' : extraTaggedValues,
     'valueType'    : valueType
    }
    
    # Set default documentation for special attributes
    if not documentation:
      if name == ImpConstants.serial_attribute:
        params['documentation'] = (
         "Serial number of object. Serves as object main key."
         " Serial numbers of deleted objects are not re-used."
         " Serial numbers can only be set by the implementation."
         " Values are in practice always positive, since negative"
         " values are interpreted as a signal to set the next free serial"
        )
      if name == ImpConstants.details_attribute:
        params['documentation'] = (
         "Free text, for notes, explanatory comments, etc."
        )
    
    # get scope
    scope = str(odAttr.getTargetScope())
    if scope == 'instance':
      params['scope'] = ImpConstants.instance_level
    elif scope == 'type':
      params['scope'] = ImpConstants.classifier_level
    else:
      raise MemopsError('%s.%s : scope %s not supported' 
       % (odClass.qualifiedName, name, scope)
      )
    
    # get visibility
    params['visibility'] = str(odAttr.getVisibility()) + '_vis'
    
    # get changeability
    changeability = str(odAttr.getChangeability())
    if changeability == 'none':
      params['changeability'] = ImpConstants.changeable
    elif changeability == 'frozen':
      params['changeability'] = ImpConstants.frozen
    elif changeability == 'Add Only':
      params['changeability'] = ImpConstants.add_only
    else:
      raise MemopsError('%s.%s : changeability %s not supported' % 
       (odClass.qualifiedName, name, changeability)
      )
    
    # get isDerived
    for tag in ('isDerived', 'isAutomatic', 'isAbstract', 'isImplementation'):
      if tagVals.get(tag) == trueString:
        params[tag] = True
      else:
        params[tag] = False
    
    # get multiplicity
    mul = odAttr.getMultiplicity()
    nn = mul.getCount()
    #
    if nn == 0:
      pass
    #
    elif nn == 1:
      # one multiplicity range - process it
      rr = mul.getRange(0)
      params['locard'] = max(rr.low,0)
      hicard = rr.high
      if hicard < 1:
        hicard = genConstants.infinity
      params['hicard'] = hicard
      if hicard != 1:
        params.update(defaultMultiAttrMultiplicity)
    #
    else:
      # multiple ranges - throw error
      raise MemopsError('%s.%s has illegal multipicity indicator : %s' % 
       (odClass.qualifiedName, name, mul.asText())
      )
    #
    # fix missing parameters
      
    # get isOrdered
    if tagVals.get('isOrdered') == trueString:
      params['isOrdered'] = True
    elif tagVals.get('isOrdered') == falseString:
      params['isOrdered'] = False
 
    # get isUnique
    if tagVals.get('isUnique') == trueString:
      params['isUnique'] = True
    elif tagVals.get('isUnique') == falseString:
      params['isUnique'] = False
      
    # NB hicard defaults to 1
    hicard = params.get('hicard', defaultHicard)
    if hicard == 1:
      params['baseName'] = name

    else:
      ss = tagVals.get('baseName')
      if not ss:
        raise MemopsError("%s : baseName tagged value missing" 
         % odAttr.qualifiedName
        )
      params['baseName'] = ss

    # get defaultValue
    defaultValue = odAttr.getInitialValue()
    if defaultValue:
      typeCode = valueType.typeCodes.get('python')
      ff = getattr(baseDataTypeModule, typeCode).fromString
      if hicard == 1:
        try:
          params['defaultValue'] = (ff(defaultValue),)
        except:
          print ("%s error in converting default value %s"
                 % (odAttr.qualifiedName, defaultValue))
          raise
      else:
        try:
          params['defaultValue'] = tuple(map(ff,eval(defaultValue)))
        except:
          print ("%s error in converting default value %s"
                 % (odAttr.qualifiedName, defaultValue))
          raise
    
    # create attribute
    attribute = MetaModel.MetaAttribute(**params)
  
    # handle constraints
    addConstraints(attribute,odAttr)


def rolesFromOd(metaClass):
  """ get both roles from association assoc 
  and put them in appropriate metaClass
  """
  
  odClass = odObjFromQualName(metaClass.qualifiedName())
  
  for odAssoc in [x for x in odUtil.toList(odClass.startOwns()) 
                      if isinstance(x,OdUmlAssociation)]:
    
    # set up
    roleData = [{},{}]
    aes = odUtil.toList(odAssoc.startConnections())
    try:
      assert len(aes) == 2, 'Error, association must have 2 ends: %s' % aes
    except:
      print 'ERROR - link with wrong number of ends:', metaClass
      raise
    # get classes and types
    classNames = (aes[0].getType().qualifiedName,aes[1].getType().qualifiedName)

    skipAssociation = False
    for ii in range(2):
 
      try:
        clazz = metaObjFromQualName(classNames[ii])
        roleData[ii]['valueType'] = clazz
 
      except MemopsError:
 
        if OdDataRootClassName in classNames:
          # NBNB TBD
          # Link involving DataRootClass. 
          # It is OK the class on the other end is missing. Ignore
          print("WARNING, link between %s and %s accesses unknown package"
           % classNames
          )
          skipAssociation = True
 
        elif uniUtil.toBoolean(aes[ii].isNavigable()):
          # class on the other end reachable, but not defined
          print "ERROR for link between %s and %s" % classNames
          raise
 
        else:
          # The class on the other end is not defined but also not reachable
          # This is a one-way link into a class *into* the model
          # Ignore
          skipAssociation = True
    
    if skipAssociation:
      continue
    
    metaClasses = [roleData[1]['valueType'], roleData[0]['valueType']]
 
    # get tagged values from Association
    assoTagVals, assoExtraTaggedValues = taggedValuesFromOd(odAssoc, 'MetaAssociation')
    
    #  get isDerived, isAutomatic isAbstract
    for tag in ('isDerived', 'isAutomatic', 'partitionsChildren', 
                'isImplementation'):
      if assoTagVals.get(tag) == trueString:
        x = True
      else:
        x = False
      roleData[0][tag] = roleData[1][tag] = x
  
    # get isAbstract
    x = uniUtil.toBoolean(odAssoc.isAbstract())
    roleData[0]['isAbstract'] = roleData[1]['isAbstract'] = x
 
    # get rest of parameters, from association ends
    for ii in range(2):
      otherii = 1 - ii
      ae = aes[ii]
      dd = roleData[ii]
 
      # get metaClass -needed for error messages only
      metaClass = roleData[otherii]['valueType']
 
      # get name(s) (first pass)
      tempRoleName = ae.name
      if not tempRoleName:
        tempRoleName = uniUtil.lowerFirst(dd['valueType'].name)
      
      # get tagged values
      tv, etv = taggedValuesFromOd(ae,'MetaRole')
 
      tagVals = assoTagVals.copy()
      tagVals.update(tv)
      extraTaggedValues = assoExtraTaggedValues.copy()
      extraTaggedValues.update(etv)
      
      # NBNB TBD
      if extraTaggedValues.get('isImplementation'):
        print 'ERROR implementation role %s.%s' % (metaClass,tempRoleName)
 
      # get documentation and tagged values
      dd['documentation'] = ae.getDocumentation() or None
      dd['taggedValues'] = extraTaggedValues
 
      # get scope
      scope = str(ae.getTargetScope())
      if scope == 'instance':
        dd['scope'] = ImpConstants.instance_level
      elif scope == 'type':
        dd['scope'] = ImpConstants.classifier_level
      else:
        raise MemopsError('%s.%s : scope %s not supported' %
         (metaClass.qualifiedName(),tempRoleName,scope)
        )
 
      # get visibility
      dd['visibility'] = str(ae.getVisibility()) + '_vis'
 
      # get changeability
      changeability = str(ae.getChangeability())
      if changeability == 'none':
        dd['changeability'] = ImpConstants.changeable
      elif changeability == 'frozen':
        dd['changeability'] = ImpConstants.frozen
      elif changeability == 'Add Only':
        dd['changeability'] = ImpConstants.add_only
      else:
        raise MemopsError('%s.%s : changeability %s not supported' %
         (metaClass.qualifiedName(),tempRoleName,changeability)
        )
 
      # get aggregation
      aggregation = ae.getAggregation()
      if aggregation == aggregation.COMPOSITE:
        dd['aggregation'] = ImpConstants.composite_aggregation
        dd['hierarchy'] = ImpConstants.parent_hierarchy
        roleData[otherii]['hierarchy'] = ImpConstants.child_hierarchy
      elif aggregation == aggregation.AGGREGATE:
        # aggregate is not supported, but for completeness
        try:
          dd['aggregation'] = ImpConstants.aggregate_aggregation
        except:
          raise MemopsError("%s.%s aggregation 'aggregate' not supported" %
           (metaClass.qualifiedName(), tempRoleName,)
          )
      else:
        dd['aggregation'] = ImpConstants.no_aggregation
 
      # get multiplicity
      mul = ae.getMultiplicity()
      nn = mul.getCount()
      #
      if nn == 0:
        pass
      #
      elif nn == 1:
        # one multiplicity range - process it
        rr = mul.getRange(0)
        dd['locard'] = max(rr.low,0)
        hicard = rr.high
        if hicard < 1:
          hicard = genConstants.infinity
        dd['hicard'] = hicard
        if hicard != 1:
          dd.update(defaultMultiRoleMultiplicity)
      #
      else:
        # multiple ranges - throw error
        raise MemopsError('%s.%s has illegal multipicity indicator : %s' %
         (metaClass.qualifiedName(), tempRoleName, mul.asText())
        )
      #
      # fix missing parameters
      # NB hicard efaults to 1
      if dd.get('hicard', defaultHicard) == 1:
        dd['baseName'] = tempRoleName
 
      else:
        baseName = tagVals.get('baseName')
        if baseName is None:
          if ae.name:
            # explicit names require explicit basenames (to reduce errors)
            raise MemopsError(
             "%s.%s has explicit name, but no baseName tagged value"
             % (metaClass.qualifiedName(), tempRoleName)
            )
          else:
            baseName = uniUtil.lowerFirst(dd['valueType'].name)
 
        dd['baseName'] = baseName
 
      # get isUnique
      if tagVals.get('isUnique') == trueString:
        dd['isUnique'] = True
      elif tagVals.get('isUnique') == falseString:
        dd['isUnique'] = False

      # get isOrdered
      if ae.isOrdered():
        dd['isOrdered'] = True
      else:
        dd['isOrdered'] = False
 
    # create both roles:
    doRoles = []
    for ii in range(2):
      if uniUtil.toBoolean(aes[ii].isNavigable()):
        baseName = roleData[ii]['baseName']
        # NB hicard defaults to 1
        if roleData[ii].get('hicard', defaultHicard) == 1:
          name = baseName
        else:
          name = aes[ii].name
          if not name:
            name = baseName + 's'
        roleData[ii]['name'] = name
        doRoles.append(ii)
      
    if len(doRoles) == 1:
 
      # only navigable one direction. Create
      ii = doRoles[0]
      
      # get guid
      guid = getGuid(aes[ii])
 
      role = MetaModel.MetaRole(container=metaClasses[ii], guid=guid,
                                **roleData[ii])
      addConstraints(role,aes[ii])
 
    elif len(doRoles) == 2:
      # navigable both directions.
 
      if (metaClasses[0] is metaClasses[1] and 
          roleData[0]['name'] == roleData[1]['name']):
        # Special case - symmetrical to-own-class link. Create one, shared role.
        # Equalise documentation and check roles are identical
 
        if not roleData[0].get('documentation'):
          roleData[0]['documentation'] = roleData[1].get('documentation')
 
        elif not roleData[1].get('documentation'):
          roleData[1]['documentation'] = roleData[0].get('documentation')
 
        if roleData[0] ==  roleData[1]:
          # OK for special case, implement:
          
          # first arrange guid
          guids = [x.getTaggedValueVal('guid') for x in aes]
          if guids[0] == guids[1]:
            guid = guids[0]
            if not guid:
              guid = newGuid()
              aes[0].setTaggedValueVal('guid', guid)
              aes[1].setTaggedValueVal('guid', guid)
              
          else:
            raise MemopsError("two OD roles named %s have different guid" 
                              % aes[0].qualifiedName)
          # now make role
          role = MetaModel.MetaRole(container=metaClasses[0], guid=guid,
                                    **roleData[0])
          role.setOtherRole(role)
 
          addConstraints(role,aes[ii])
 
        else:
          raise MemopsError("%s.%s Invalid symmetrical -to-self association." %
           (metaClasses[0].name, roleData[0].get('name'))
          )
 
      else:
        # normal two-way link. Implement
        guid = getGuid(aes[0])
        role  = MetaModel.MetaRole(container=metaClasses[0], guid=guid, 
                                   **roleData[0])
        guid = getGuid(aes[1])
        role1 = MetaModel.MetaRole(container=metaClasses[1], guid=guid, 
                                   **roleData[1])
        role.setOtherRole(role1)
 
        # add constraints
        foundConstraints = addConstraints(role,aes[0])
        if foundConstraints and role.hicard != 1 and role1.hicard == 1:
          raise MemopsError('Constraints on wrong side of one-to-many link %s.%s'
           % (role.container.name, role.name))
        foundConstraints = addConstraints(role1,aes[1])
        if foundConstraints and role1.hicard != 1 and role.hicard == 1:
          raise MemopsError('Constraints on wrong side of one-to-many link %s.%s'
           % (role1.container.name, role1.name)
          )
 
    else:
      raise MemopsError(
       "Link between classes %s One or two roles must be navigable." %
       (tuple(classNames),)
      )



def operationsFromOd(metaClass):
  """ get all operations from odClass and put them in appropriate metaClass
  """
  
  odClass = odObjFromQualName(metaClass.qualifiedName())
  
  for odOp in odUtil.toList(odClass.operationElements()):
  
    name = odOp.name
    guid = getGuid(odOp)
    
    tagVals, extraTaggedValues = taggedValuesFromOd(odOp,'MetaOperation')
    
    #
    params = {
     'container'    : metaClass,
     'name'         : name,
     'guid'         : guid,
     'documentation': odOp.getDocumentation() or None,
     'taggedValues' : extraTaggedValues
    }
  
    # set  code stubs
    dd = tagVals.get('code')
    if dd:
      params['codeStubs'] = dd
    
    # get scope. NBNB we use owner scope as here is no target scope WOT IS ???!!
    scope = str(odOp.getOwnerScope())
    if scope == 'instance':
      params['scope'] = ImpConstants.instance_level
    elif scope == 'type':
      params['scope'] = ImpConstants.classifier_level
    else:
      raise MemopsError('%s : scope %s not supported' %
      (odOp.qualifiedName,scope)
     )
    
    # get visibility
    params['visibility'] = str(odOp.getVisibility()) + '_vis'
    
    # get isQuery
    params['isQuery'] = uniUtil.toBoolean(odOp.isQuery())
    
    # get isAbstract
    params['isAbstract'] = uniUtil.toBoolean(odOp.isAbstract())
    
    # get opType
    params['opType'] = tagVals.get('opType')
    params['opSubType'] = tagVals.get('opSubType')
    if params['opType'] is None:
      raise MemopsError('%s : operation lacks opTyped' % (odOp.qualifiedName))
    
    # make operation
    metaOperation = MetaModel.MetaOperation(**params)
    
    # add parameters
    parametersFromOd(metaOperation)
    

def parametersFromOd(metaOp):
  """ get all parameters for odOperation and add as appropriate
  NB requires that dataTypes are already read and postprocessed.
  """
  
  opType = metaOp.opType
  opTargetTag = OpTypes.operationData[opType]['targetTag']
  
  odOp = odObjFromQualName(metaOp.qualifiedName())
  
  if opTargetTag == 'masterOp':
    # add parameters
 
    for odPar in odUtil.toList(odOp.startParameters()):
 
      # get parameter name
      name = odPar.name
      guid = getGuid(odPar)
 
      # get parameter type
      try:
        valueType = metaObjFromQualName(odPar.getTypeRef().qualifiedName)
      except:
        print ("Error setting type of %s to %s" %
         (odPar.qualifiedName, `odPar.getTypeRef()`)
        )
        raise
 
      # get tagged values
      tagVals, extraTaggedValues = taggedValuesFromOd(odPar, 'MetaParameter')
 
      # get documentation
      params = {
       'container'    : metaOp,
       'name'         : name,
       'guid'         : guid,
       'documentation': odPar.getDocumentation() or None,
       'taggedValues' : extraTaggedValues,
       'valueType'    : valueType
      }
 
      # get parameter direction
      params ['direction'] = str(odPar.getKind()) + '_dir'
 
      # get cardinality etc. from typeExpression
      ss = odPar.getTypeExpression().getBody()
      params.update(parseMultiplicity(ss))
      hicard = params.get('hicard',defaultHicard)
      if hicard != 1:
        params.update(defaultMultiParMultiplicity)
 
      # get defaultValue
      defaultValue = odPar.getDefaultValue()
      if defaultValue:
        typeCode = valueType.typeCodes.get('python')
        ff = getattr(baseDataTypeModule, typeCode).fromString
        if hicard == 1:
          params['locard'] = 0
          try:
            params['defaultValue'] = (ff(defaultValue),)
          except:
            print ("%s error in converting default value %s"
                   % (odPar.qualifiedName, defaultValue))
            raise
        else:
          # NB MetaParameters with hicard != 1 can *not* have defaults currently
          # This will raise an error on validation, so we might as well leave it
          # as is.
          try:
            params['defaultValue'] = tuple(map(ff,eval(defaultValue)))
          except:
            print ("%s error in converting default value %s"
                   % (odPar.qualifiedName, defaultValue))
            raise
 
      # create parameter
      MetaModel.MetaParameter(**params)
    
  else:
    # there should be no parameters - write warning and delete.
    for odPar in odUtil.toList(odOp.startParameters()):

      # get parameter name
      name = odPar.name
      print ("WARNING %s, opType %s has explicit parameter %s - deleting"
       % (metaOp, opType, name)
      )
      odOp.removeParameter(odPar)

def taggedValuesFromOd(odElement, elementType):
  """ get tag:value dictionary for an od Element,
  checking against localAllowedVals dictionary
  """
  
  # set up
  dd = {}
  taggedValues = {}
  extraTaggedValues = {}
  
  localAllowedVals = allowedTags[elementType]
  
  specialAllowedTags = ('constructorCode:', 'destructorCode:', 
                        'typeCode:', 'code:')
  
  # compatibility for old to new MetaModel NBNB HACK
  compatibilityTaggedValues(odElement, elementType)
  
  # start processing
  odTagvals = odUtil.toList(odElement.startTaggedValues())
  for tt in odTagvals:
    dd[tt.name] = tt.getValue()
  
  for kk,vv in dd.items():
  
    if localAllowedVals.has_key(kk):
      allowedVals = localAllowedVals[kk]
      
      if allowedVals is None or vv in allowedVals:
        taggedValues[kk] = vv
      
      else:
        print 'localAllowedVals', localAllowedVals
        raise MemopsError("Illegal tagged value %s:%s for %s %s" 
                          % (kk, `vv`, elementType, odElement.qualifiedName))
        
    else:
      # check for special tags
      for specialTag in specialAllowedTags:
        if kk.startswith(specialTag):
          allowedVals = localAllowedVals[specialTag]
      
          if allowedVals is None or vv in allowedVals:
            ss = specialTag[:-1]
            dd = taggedValues.get(ss)
            if dd is None:
              dd = taggedValues[ss] = {}
            dd[kk[len(specialTag):]] = vv
            
          else:
            raise MemopsError("Illegal tagged value %s:%s for %s %s" 
                              % (kk, `vv`, elementType, odElement.qualifiedName))
          break
          
      else:
        # pass on as tagged value
        extraTaggedValues[kk] = vv
  
  #
  return taggedValues, extraTaggedValues


def compatibilityTaggedValues(odElement, elementType):
  """ Modify tagged values to update to new machinery (June 2006)
  
  NBNB HACK
  """
  
  # set up taggedValues dictionary
  taggedValues = {}
  for tt in odUtil.toList(odElement.startTaggedValues()):
    taggedValues[tt.name] = tt
  
  # rename tagged values
  for oldtag,newtag in renameTags.items():
    tv = taggedValues.get(oldtag)
    if tv is not None:
      odElement.setTaggedValueVal(newtag,tv.getValue())
      odElement.removeTaggedValue(tv)
  
  # skip tagged values
  for oldtag in skipTags:
    tv = taggedValues.get(oldtag)
    if tv is not None:
      odElement.removeTaggedValue(tv)


def addConstraints(metaObj, odObj):
  """ add constraints to metaObject given odObject
  returns True if constraints were found, otherwise False.
  """
  
  odCons = odUtil.toList(odObj.startConstraints())
  
  if odCons:
  
    for odCon in odCons:
      
      try:
        codeStubs = parseConstraint(odCon)
      
      except:
        print ("Error processing %s constraint :\n %s" % 
         (odObj.qualifiedName, odCon.name)
        )
        raise
      
      # temp debug info NBNB
      if not odCon.getTaggedValueVal('guid'):
        print ' NO guid for ', metaObj, odCon.getName()
      
      # get or set guid
      guid = getGuid(odCon)
          
      params = {'name':odCon.name, 'guid':guid, 'codeStubs':codeStubs}
    
      MetaModel.MetaConstraint(container=metaObj, **params)
    
    #
    return True
  
  else:
    return False


def parseConstraint(odCon):
  """Unpack constraint code tagged values.
  """
      
  body = odCon.getBody()
  # make sure lineseparator is '\n'
  ss = '\n'.join(body.splitlines())
  
  # parse body and make tagged values
  ll = ss.split('\n_\n')
  result = {}
  
  length = len(ll)
  if length <= 1:
    raise MemopsError("invalid code tagged value: \n%s\n" % ss)
  if length % 2:
    raise MemopsError(
     "code tagged value has wrong number of separators: \n%s\n" % ss
    )
    
  
  try:
    ii=0
    while ii < len(ll):
      tag = ll[ii].strip()
      value = ll[ii+1] 
      # next line to ensure multiline code tags end in newline
      if '\n' in value and '\n' != value[-1]:
        value = value + '\n'
       
      result[tag] = value
      ii = ii + 2
  except:
    print(
     "Illegal value for code taggedvalue: \n%s" % ss
    )
    raise
  
  # compatibility for old to new MetaModel
  # NBNB HACK
  rewrite = False
  for tag,val in result.items():
    newTag = renameTags.get(tag)
    if newTag is not None:
      rewrite = True
      del result[tag]
      result[newTag] = val
  
  if rewrite:
    # Compatibility contd. NBNB HACK - change stored version of body
    ll = []
    for tag,val in result.items():
      # set up for writing
      ll.append(tag)
      ll.append(val)
    odCon.setBody('\n_\n'.join(ll))
  
  # convert tag names, removing 'code:'
  for tag,val in result.items():
    if tag[:5] == 'code:':
      del result[tag]
      result[tag[5:]] = val
  
  #
  return result

def odObjFromQualName(qname):
  """ find odObject given qualified name
  """
  
  result = od.get(qname)
  
  if result is None:
   raise MemopsError("No OdObject corresponding to %s" % qname)
   
  else:
    return result

def metaObjFromQualName(qname):
  """ find MetaObject given qualified name.
  If no success, remove default objectdomain prefix ('Model.Logical')
  and try again
  """
  
  try:
    # get using qualified name
    return rootPackage.metaObjFromQualName(qname)
    
  except MemopsError:
    pass
  
  except:
    print """An unexpected error occurred. Causes could be:
    1) Some modules but not others have been reloaded. 
       Try reloading all modules or restarting
    2) A coding error (bug)."""
    raise
    
  try:
    # get after first removing OdContainingPackageName
    nn = len(OdContainingPackageName) + 1
    if qname[:nn] == OdContainingPackageName + '.':
      return rootPackage.metaObjFromQualName(qname[nn:])
  
  except MemopsError:
    # no result. Raise error(using full qname)
    print("ERROR: No MetaObject corresponding to %s can be found" 
     % qname
    )
    raise
  
  # no result - raise error
  raise MemopsError("No MetaObject corresponding to %s can be found" % qname)


def getGuid(odElement):
  """ return guid tagged value
  
  If none is set, get a new globally unique ID and set it as a tagged value
  """
  guid = odElement.getTaggedValueVal('guid')
  
  if not guid:
    guid = newGuid()
    odElement.setTaggedValueVal('guid', guid)
  
  return guid


def parseMultiplicity(value):
  """ parse multiplicity string and return dictionary with values
  for hicard, locard, isOrdered, and isUnique (if set)
  
  Input string can contain:
  a single multiplicity string of the form 'n..m', 'n', '*', or 'n..*',
  where n and m are integers.
  The following strings as uniqueness and ordering indicators:
  '+u', '-u', '+o', '-o', 'unordered', 'ordered', 'nonunique', 'unique'.
  
  The mulitplicity string must precede the uniqueness and ordering indicators.
  NB Note that the strings '-unique' and '-ordered' are interpreted as
  'nonunique' and 'unordered' respectively.
  
  If string is empty an empty dictionary is returned
  """
  
  indicators = (
   ('+u','isUnique',True),
   ('unique','isUnique',True),
   ('-u','isUnique',False), 
   ('nonunique','isUnique',False), 
   ('+o','isOrdered',True),
   ('ordered','isOrdered',True),
   ('-o','isOrdered',False),
   ('unordered','isOrdered',False),
  )
  mTags = '0123456789*'
  
  result = {}
  
  if value:
  
    # find uniqueness and order info. NB it is deliberate that e.g.
    # 'unordered' overrides 'ordered'
    end = len(value)
    for ss,tag,val in indicators:
      ii = value.find(ss)
      if ii != -1:
        end = min(end,ii)
        result[tag] = val
 
    # restrict string to potential multiplicity indicator
    start = 0
    while start < end:
      if value[start] in mTags:
        break
      else:
        start = start + 1
 
    while end > start:
      jj = end - 1
      if value[jj] in mTags:
        break
      else:
        end = jj
 
    # find multiplicity
    locard,hicard = metaUtil.parseCardinality(value[start:end])
    result['locard'] = locard
    result['hicard'] = hicard
    
  #
  return result
by Rasmus Fogh last modified 2007-05-09 15:24

Powered by Plone, the Open Source Content Management System

This site conforms to the following standards: