"""Module Documentation here
"""
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (http://www.ccpn.ac.uk) 2014 - 2017"
__credits__ = ("Wayne Boucher, Ed Brooksbank, Rasmus H Fogh, Luca Mureddu, Timothy J Ragan & Geerten W Vuister")
__licence__ = ("CCPN licence. See http://www.ccpn.ac.uk/v3-software/downloads/license",
"or ccpnmodel.ccpncore.memops.Credits.CcpnLicense for licence text")
__reference__ = ("For publications, please use reference from http://www.ccpn.ac.uk/v3-software/downloads/license",
"or ccpnmodel.ccpncore.memops.Credits.CcpNmrReference")
#=========================================================================================
# Last code modification
#=========================================================================================
__modifiedBy__ = "$modifiedBy: CCPN $"
__dateModified__ = "$dateModified: 2017-07-07 16:33:17 +0100 (Fri, July 07, 2017) $"
__version__ = "$Revision: 3.0.0 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: CCPN $"
__date__ = "$Date: 2017-04-07 10:28:48 +0000 (Fri, April 07, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================
"""
======================COPYRIGHT/LICENSE START==========================
Converters1.py: Data compatibility handling
Copyright (C) 2007 Rasmus Fogh (CCPN project)
=======================================================================
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
A copy of this license can be found in ../../../../license/LGPL.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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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============================
To obtain more information about this code:
- CCPN website (http://www.ccpn.ac.uk)
- contact Rasmus Fogh (ccpn@bioc.cam.ac.uk)
=======================================================================
If you are using this software for academic purposes, we suggest
quoting the following reference:
===========================REFERENCE START=============================
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===============================
"""
# imports
import importlib
from ccpnmodel.ccpncore.lib import Conversion
from ccpnmodel.ccpncore.memops import Version
from ccpnmodel.ccpncore.memops.ApiError import ApiError
from ccpnmodel.ccpncore.memops.metamodel import Constants as metaConstants
currentVersion = Version.currentModelVersion
mapInfoLocation = 'ccpnmodel.ccpncore.memops.format.compatibility'
[docs]def modifyIoMap(fromVersionStr, globalMapping):
""" Adapt globalMapping to read oldVersion data
"""
# HACK - 2,0,6 is aside branch, and a lot of extra work to upgrade correctly.
# Treating it as identical to 2.0.5 should (hopefully) work most of the time.
# Alternatively one could try treating it as 2.1.0 instead
if fromVersionStr == '2.0.6':
fromVersionStr = '2.0.5'
fromVersion = Version.Version(fromVersionStr)
infodd = Conversion.getConversionInfo(fromVersion, currentVersion)
# Model information by guid
anApiClass = globalMapping['IMPL']['abstractTypes']['MemopsObject']['class']
newElementsByGuid = getElementsByGuid(anApiClass._metaclass.topPackage())
# new elements retrofitted by hand and therefore treated as old
# used for e.g. changes of parent tree.
elemsTreatedAsOld = set(infodd['elemsTreatedAsOld'])
diffMap = importlib.import_module('%s.%s.MapInfo' % (mapInfoLocation,fromVersion.getDirName()))
# correct maps for newly introduced elements
for tag in ('newElements', 'newMandatories', 'neutraliseElements'):
ll = getattr(diffMap, tag)
for (prefix, typeName, elemName, newGuid) in ll:
if prefix in globalMapping and newGuid not in elemsTreatedAsOld:
removeElementName(newGuid, globalMapping, newElementsByGuid)
# correct maps for skipped elements
for (prefix, typeName, elemName, newGuid, elemType) in diffMap.skipElements:
if prefix in globalMapping and newGuid not in elemsTreatedAsOld:
fixElementMap(newGuid, 'skip', prefix, globalMapping, newElementsByGuid,
typeName, elemName)
if elemType == 'MetaClass':
fixExoLinkMap(newGuid, 'skip', prefix, globalMapping, typeName)
# correct maps for changed valueTypes
for tt in diffMap.typeChanges:
(prefix, typeName, elemName, action, newGuid, elemMap, valueTypeGuid) = tt
if prefix in globalMapping and newGuid not in elemsTreatedAsOld:
fixElementMap(newGuid, action, prefix, globalMapping, newElementsByGuid,
typeName, elemName, valueTypeGuid=valueTypeGuid,
elemMap=elemMap, overrideGuidMap=True)
# correct maps for renamed elements
# NB must be done after valueTypes corrections,
# as elements may be both renamed and 'delay'ed or 'skip'ped
# by changed valueType
for (prefix, typeName, elemName, newName, newGuid) in diffMap.renames:
if prefix in globalMapping and newGuid not in elemsTreatedAsOld:
fixElementMap(newGuid, 'rename', prefix, globalMapping, newElementsByGuid,
typeName, elemName)
if elemName is None:
fixExoLinkMap(newGuid, 'rename', prefix, globalMapping, typeName)
dd = globalMapping['mapsByGuid']['www.ccpn.ac.uk_Fogh_2006-08-16-18:20:12_00012']
# correct maps for delayed elements
# NB, must be done after renaming, as we use type names to find
# appropriate maps
for tt in diffMap.delayElements:
(prefix, typeName, elemName, newGuid, elemMap, valueTypeGuid) = tt
if prefix in globalMapping and newGuid not in elemsTreatedAsOld:
fixElementMap(newGuid, 'delay', prefix, globalMapping, newElementsByGuid,
typeName, elemName, valueTypeGuid=valueTypeGuid,
elemMap=elemMap)
# make extra changes
infodd['extraMapChanges'](globalMapping)
[docs]def removeElementName(guid, globalMapping, newElementsByGuid):
""" Remove element from name lists used for selecting input
and remove element from loadMaps (in case another element with the
same signature needs the slot).
"""
# set up
useObj = newElementsByGuid.get(guid)
if useObj is None:
# guid does not exist. Probably was not included in model generation.
# Anyway, nothing to do.
return
newName = useObj.name
mapsByGuid = globalMapping['mapsByGuid']
# remove from lists in container and its subtypes
if useObj.__class__.__name__ in ('MetaAttribute', 'MetaRole'):
if (useObj.isAbstract
or (useObj.isDerived and useObj.changeability == metaConstants.frozen)):
# These elements have no maps, so nothing to do
return
subtypes = useObj.container.getAllSubtypes()
for metaclass in subtypes:
containerMap = mapsByGuid.get(metaclass.guid)
if containerMap is not None:
# NB there might not be a map if this is (e.g.) an abstract superclass
# that does not inherit from DataObject
for tag in ('simpleAttrs', 'headerAttrs', 'optLinks', 'cplxAttrs',
'children'):
ll = containerMap.get(tag)
if ll and newName in ll:
ll.remove(newName)
# remove from loadMaps
oldMap = mapsByGuid.get(guid)
if oldMap is None:
# check if there is need for concern
if (useObj.__class__.__name__ == 'MetaRole' and
useObj.container.container in useObj.valueType.container.importedPackages):
# interpackage link in backwards direction
pass
elif (useObj.__class__.__name__ == 'MetaRole' and
useObj.hierarchy == 'parent'):
pass
elif useObj.container.guid not in mapsByGuid:
# The class is not mapped.
# Could be (is?) abstract multi-inheritance class
pass
else:
print('WARNING, no previous map found for %s (%s)' % (useObj, guid))
else:
del globalMapping['loadMaps'][oldMap['tag']]
[docs]def fixElementMap(newGuid, action, prefix, globalMapping, newElementsByGuid,
typeName=None, elemName=None, overrideGuidMap=False,
valueTypeGuid=None, elemMap=None):
""" insert/modify I/O map in mapping
- newGuid is the guid to use in the new map
(new guid if element exists, old guid otherwise)
- prefix is the package shortname
- typeName is the name of the class/DataObjType (if any)
- elemName is the name of the ClassElement (if any)
- overrideGuidMap controls if an existing map should be overridden
- valueTypeGuid is the guid for a new valueType (for attr)
- elemMap is a passed-in ({typ, proc, name, eType}) I/O map for the element
"""
from ccpnmodel.ccpncore.memops.format.xml import XmlGen
if action == 'ignore':
return
elif action not in ('rename', 'skip', 'delay'):
raise ApiError("action: %s not supported" % action)
loadMaps = globalMapping['loadMaps']
mapsByGuid = globalMapping['mapsByGuid']
mapping = globalMapping[prefix]
if typeName is None:
return
xmlTag = XmlGen.xmlTag(prefix, typeName, elemName)
if elemName is None:
typ = 'dataType'
else:
typ = 'elem'
if action == 'skip':
# we are skipping the element
if newGuid in mapsByGuid:
raise ApiError("%s: guid already has map" % xmlTag)
else:
mapsByGuid[newGuid] = newMap = {'guid':newGuid, 'proc':'skip'}
if typ == 'dataType':
# put map in abstractTypes
abstractTypes = mapping['abstractTypes']
if typeName in abstractTypes:
raise ApiError("%s: name %s already in use" % (xmlTag, typeName))
else:
abstractTypes[typeName] = newMap
elif action == 'rename':
# this is a renaming
# get previous I/O map (must exist)
newMap = mapsByGuid.get(newGuid)
if newMap is None:
# guid does not exist. Probably was not included in model generation.
# Anyway, nothing to do.
return
# clean up loadMaps
del loadMaps[newMap['tag']]
if typ == 'elem':
# ClassElement
newObj = newElementsByGuid[newGuid]
newName = newObj.name
if newName != elemName:
# they may be equal if the container class only is being renamed
# fix attribute lists and contents for container and its subtypes
subtypes = newObj.container.getAllSubtypes()
for metaclass in subtypes:
fixMap = mapsByGuid[metaclass.guid]
# fix content map
contDict = fixMap['content']
if elemName in contDict:
raise ApiError("%s: name %s already in use" % (xmlTag, elemName))
contDict[elemName] = contDict[newName]
del contDict[newName]
# fix attribute lists
for tag in ('simpleAttrs', 'headerAttrs', 'optLinks', 'cplxAttrs'):
# For technical reasons 'children' must stay with the original name:
# for tag in ('simpleAttrs', 'headerAttrs', 'optLinks', 'cplxAttrs',
# 'children'):
ll = fixMap.get(tag)
if ll and newName in ll:
ll[ll.index(newName)] = elemName
else:
# classifier - still needs renaming
# as we use the type name to find the right map
# put map in abstractTypes
abstractTypes = mapping['abstractTypes']
if typeName in abstractTypes:
raise ApiError("%s: name %s already in use" % (xmlTag, typeName))
else:
abstractTypes[typeName] = newMap
else:
# action == 'delay'
# we are delaying storage for use in compatibility
if typ != 'elem':
raise ApiError("%s: only ClassElements may be 'delay'ed" % xmlTag)
if valueTypeGuid is not None:
elemMap['data'] = mapsByGuid[valueTypeGuid]
if overrideGuidMap:
newMap = mapsByGuid[newGuid]
elif newGuid in mapsByGuid:
raise ApiError("%s: guid %s already in use" % (xmlTag, newGuid))
elif xmlTag in loadMaps:
raise ApiError("%s: xml tag already in use" % (xmlTag,))
else:
mapsByGuid[newGuid] = newMap = {}
containerGuid = mapping['abstractTypes'][typeName]['guid']
newContainer = newElementsByGuid[containerGuid]
subtypes = newContainer.getAllSubtypes()
for metaclass in subtypes:
# put new map in content dictionary
fixMap = mapsByGuid[metaclass.guid]
contDict = fixMap['content']
if elemName in contDict:
# we are overriding the previous definition.The tag is already there
pass
else:
# put tag on cplxAttrs (it does not matter which list it is on)
contDict[elemName] = newMap
ll = fixMap.setdefault('cplxAttrs', [])
ll.append(elemName)
# update map contents
newMap.update(elemMap)
newMap['proc'] = 'delay'
# fix loadMaps
loadMaps[xmlTag] = newMap
[docs]def fixExoLinkMap(newGuid, action, prefix, globalMapping, typeName):
""" Fix exoLink maps
if action == 'rename' enter newGuid map under (prefix, typeName) name
if action == 'skip', enter skip record for guid under (prefix, typeName) name
"""
from ccpnmodel.ccpncore.memops.format.xml import XmlGen
exoTag = XmlGen.xmlTag(prefix, typeName, var='exo')
# set up
exolinks = globalMapping[prefix]['exolinks']
loadMaps = globalMapping['loadMaps']
# check if slots are occupied
if typeName in exolinks:
raise ApiError("%s exolink: name %s already in use" % (exoTag, typeName))
if exoTag in loadMaps:
raise ApiError("%s exolink: name already in use" % exoTag)
if action == 'skip':
# we are not replacing anything, but skippping
newMap = {
'type':'exo',
'guid':newGuid,
'eType':'cplx',
'proc':'skip'
}
else:
# replacing an existing map
newClassMap = globalMapping['mapsByGuid'].get(newGuid)
newExoTag = XmlGen.xmlTag(prefix, newClassMap['class'].__name__, var='exo')
#newMap = globalMapping['mapsByGuid'].get(newGuid)
newMap = loadMaps[newExoTag]
del exolinks[newMap['class'].__name__]
#del loadMaps[newMap['tag']]
# set new map
exolinks[typeName] = newMap
loadMaps[exoTag] = newMap
[docs]def getElementsByGuid(rootPackage):
""" get guid:obj dictionary for MetaModelElements
in MetaModel corresponding to rootPackage
Used in mapping of reference data.
NBNB may need version-specific expansion later to handle
difficult reference data modifications.
"""
from ccpnmodel.ccpncore.memops.metamodel.MetaModel import makeObjDict
#
return makeObjDict(rootPackage, ignoreImplicit=True, crucialOnly=True)
[docs]def minorPostProcess(fromVersion, topObj, delayDataDict, toNewObjDict, mapping=None):
""" postProcess - update newRoot object tree
May be used to postprocess a file load (minor upgrade)
topObj is a TopObject in the new tree
delayDataDict is a {newobj:{tag:value}} dictionary of delayed set elements
and children
toNewObjDict is _ID:newObj for minor upgrades
mapping is the IO mapping
"""
infodd = Conversion.getConversionInfo(fromVersion, currentVersion)
infodd['correctData'](topObj, delayDataDict, toNewObjDict, mapping)
# NBNB Hack: do data upgrade for V2-V3transition
# TBD remove for future versions
# if topObj.className == 'MemopsRoot':
# topObj._isUpgraded = True