Home | Trees | Indices | Help |
|
---|
|
1 """Implements base class for struct types.""" 2 3 # -------------------------------------------------------------------------- 4 # ***** BEGIN LICENSE BLOCK ***** 5 # 6 # Copyright (c) 2007-2011, Python File Format Interface 7 # All rights reserved. 8 # 9 # Redistribution and use in source and binary forms, with or without 10 # modification, are permitted provided that the following conditions 11 # are met: 12 # 13 # * Redistributions of source code must retain the above copyright 14 # notice, this list of conditions and the following disclaimer. 15 # 16 # * Redistributions in binary form must reproduce the above 17 # copyright notice, this list of conditions and the following 18 # disclaimer in the documentation and/or other materials provided 19 # with the distribution. 20 # 21 # * Neither the name of the Python File Format Interface 22 # project nor the names of its contributors may be used to endorse 23 # or promote products derived from this software without specific 24 # prior written permission. 25 # 26 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 29 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 30 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 31 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 32 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 33 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 35 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 36 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37 # POSSIBILITY OF SUCH DAMAGE. 38 # 39 # ***** END LICENSE BLOCK ***** 40 # -------------------------------------------------------------------------- 41 42 # note: some imports are defined at the end to avoid problems with circularity 43 44 from functools import partial 45 from itertools import izip 46 47 from pyffi.utils.graph import DetailNode, GlobalNode, EdgeFilter 48 import pyffi.object_models.common51 """This metaclass checks for the presence of _attrs and _is_template 52 attributes. For each attribute in _attrs, an 53 <attrname> property is generated which gets and sets basic types, 54 and gets other types (struct and array). Used as metaclass of 55 StructBase."""13657 super(_MetaStructBase, cls).__init__(name, bases, dct) 58 # does the type contain a Ref or a Ptr? 59 cls._has_links = getattr(cls, '_has_links', False) 60 # does the type contain a Ref? 61 cls._has_refs = getattr(cls, '_has_refs', False) 62 # does the type contain a string? 63 cls._has_strings = getattr(cls, '_has_strings', False) 64 for attr in dct.get('_attrs', []): 65 # basestring is a forward compound type declaration 66 # and issubclass must take a type as first argument 67 # hence this hack 68 if not isinstance(attr.type_, basestring) and \ 69 issubclass(attr.type_, BasicBase) and attr.arr1 is None: 70 # get and set basic attributes 71 setattr(cls, attr.name, property( 72 partial(StructBase.get_basic_attribute, name=attr.name), 73 partial(StructBase.set_basic_attribute, name=attr.name), 74 doc=attr.doc)) 75 elif not isinstance(attr.type_, basestring) and \ 76 issubclass(attr.type_, StructBase) and attr.arr1 is None: 77 # get and set struct attributes 78 setattr(cls, attr.name, property( 79 partial(StructBase.get_attribute, name=attr.name), 80 partial(StructBase.set_attribute, name=attr.name), 81 doc=attr.doc)) 82 elif attr.type_ == type(None) and attr.arr1 is None: 83 # get and set template attributes 84 setattr(cls, attr.name, property( 85 partial(StructBase.get_template_attribute, name=attr.name), 86 partial(StructBase.set_template_attribute, name=attr.name), 87 doc=attr.doc)) 88 else: 89 # other types of attributes: get only 90 setattr(cls, attr.name, property( 91 partial(StructBase.get_attribute, name=attr.name), 92 doc=attr.doc)) 93 94 # check for links and refs and strings 95 if not cls._has_links: 96 if attr.type_ != type(None): # templates! 97 # attr.type_ basestring means forward declaration 98 # we cannot know if it has links, so assume yes 99 if (isinstance(attr.type_, basestring) 100 or attr.type_._has_links): 101 cls._has_links = True 102 #else: 103 # cls._has_links = True 104 # or false... we can't know at this point... might be necessary 105 # to uncomment this if template types contain refs 106 107 if not cls._has_refs: 108 if attr.type_ != type(None): 109 # attr.type_ basestring means forward declaration 110 # we cannot know if it has refs, so assume yes 111 if (isinstance(attr.type_, basestring) 112 or attr.type_._has_refs): 113 cls._has_refs = True 114 #else: 115 # cls._has_refs = True # dito, see comment above 116 117 if not cls._has_strings: 118 if attr.type_ != type(None): 119 # attr.type_ basestring means forward declaration 120 # we cannot know if it has strings, so assume yes 121 if (isinstance(attr.type_, basestring) 122 or attr.type_._has_strings): 123 cls._has_strings = True 124 else: 125 # enabled because there is a template key type that has 126 # strings 127 cls._has_strings = True 128 129 # precalculate the attribute list 130 # profiling shows that this speeds up most of the StructBase methods 131 # that rely on parsing the attribute list 132 cls._attribute_list = cls._get_attribute_list() 133 134 # precalculate the attribute name list 135 cls._names = cls._get_names()138 """Base class from which all file struct types are derived. 139 140 The StructBase class implements the basic struct interface: 141 it will initialize all attributes using the class interface 142 using the _attrs class variable, represent them as strings, and so on. 143 The class variable _attrs must be declared every derived class 144 interface. 145 146 Each item in the class _attrs list stores the information about 147 the attribute as stored for instance in the xml file, and the 148 _<name>_value_ instance variable stores the actual attribute 149 instance. 150 151 Direct access to the attributes is implemented using a <name> 152 property which invokes the get_attribute and set_attribute 153 functions, as demonstrated below. 154 155 See the pyffi.XmlHandler class for a more advanced example. 156 157 >>> from pyffi.object_models.xml.basic import BasicBase 158 >>> from pyffi.object_models.xml.expression import Expression 159 >>> from pyffi.object_models.xml import StructAttribute as Attr 160 >>> class SimpleFormat(object): 161 ... class UInt(BasicBase): 162 ... _is_template = False 163 ... def __init__(self, **kwargs): 164 ... BasicBase.__init__(self, **kwargs) 165 ... self.__value = 0 166 ... def get_value(self): 167 ... return self.__value 168 ... def set_value(self, value): 169 ... self.__value = int(value) 170 ... @staticmethod 171 ... def name_attribute(name): 172 ... return name 173 >>> class X(StructBase): 174 ... _is_template = False 175 ... _attrs = [ 176 ... Attr(SimpleFormat, dict(name = 'a', type = 'UInt')), 177 ... Attr(SimpleFormat, dict(name = 'b', type = 'UInt'))] 178 >>> SimpleFormat.X = X 179 >>> class Y(X): 180 ... _is_template = False 181 ... _attrs = [ 182 ... Attr(SimpleFormat, dict(name = 'c', type = 'UInt')), 183 ... Attr(SimpleFormat, dict(name = 'd', type = 'X', cond = 'c == 3'))] 184 >>> SimpleFormat.Y = Y 185 >>> y = Y() 186 >>> y.a = 1 187 >>> y.b = 2 188 >>> y.c = 3 189 >>> y.d.a = 4 190 >>> y.d.b = 5 191 >>> print(y) # doctest:+ELLIPSIS 192 <class 'pyffi.object_models.xml.struct_.Y'> instance at 0x... 193 * a : 1 194 * b : 2 195 * c : 3 196 * d : 197 <class 'pyffi.object_models.xml.struct_.X'> instance at 0x... 198 * a : 4 199 * b : 5 200 <BLANKLINE> 201 >>> y.d = 1 202 Traceback (most recent call last): 203 ... 204 TypeError: expected X but got int 205 >>> x = X() 206 >>> x.a = 8 207 >>> x.b = 9 208 >>> y.d = x 209 >>> print(y) # doctest:+ELLIPSIS 210 <class 'pyffi.object_models.xml.struct_.Y'> instance at 0x... 211 * a : 1 212 * b : 2 213 * c : 3 214 * d : 215 <class 'pyffi.object_models.xml.struct_.X'> instance at 0x... 216 * a : 8 217 * b : 9 218 <BLANKLINE> 219 """ 220 221 __metaclass__ = _MetaStructBase 222 223 _is_template = False 224 _attrs = [] 225 _games = {} 226 arg = None 227 228 # initialize all attributes650 651 from pyffi.object_models.xml.basic import BasicBase 652 from pyffi.object_models.xml.array import Array 653230 """The constructor takes a tempate: any attribute whose type, 231 or template type, is type(None) - which corresponds to 232 TEMPLATE in the xml description - will be replaced by this 233 type. The argument is what the ARG xml tags will be replaced with. 234 235 :param template: If the class takes a template type 236 argument, then this argument describes the template type. 237 :param argument: If the class takes a type argument, then 238 it is described here. 239 :param parent: The parent of this instance, that is, the instance this 240 array is an attribute of.""" 241 # used to track names of attributes that have already been added 242 # is faster than self.__dict__.has_key(...) 243 names = [] 244 # initialize argument 245 self.arg = argument 246 # save parent (note: disabled for performance) 247 #self._parent = weakref.ref(parent) if parent else None 248 # initialize item list 249 # this list is used for instance by qskope to display the structure 250 # in a tree view 251 self._items = [] 252 # initialize attributes 253 for attr in self._attribute_list: 254 # skip attributes with dupiclate names 255 # (for this to work properly, duplicates must have the same 256 # type, template, argument, arr1, and arr2) 257 if attr.name in names: 258 continue 259 names.append(attr.name) 260 261 # things that can only be determined at runtime (rt_xxx) 262 rt_type = attr.type_ if attr.type_ != type(None) \ 263 else template 264 rt_template = attr.template if attr.template != type(None) \ 265 else template 266 rt_arg = attr.arg if isinstance(attr.arg, (int, long, type(None))) \ 267 else getattr(self, attr.arg) 268 269 # instantiate the class, handling arrays at the same time 270 if attr.arr1 == None: 271 attr_instance = rt_type( 272 template = rt_template, argument = rt_arg, 273 parent = self) 274 if attr.default != None: 275 attr_instance.set_value(attr.default) 276 elif attr.arr2 == None: 277 attr_instance = Array( 278 element_type = rt_type, 279 element_type_template = rt_template, 280 element_type_argument = rt_arg, 281 count1 = attr.arr1, 282 parent = self) 283 else: 284 attr_instance = Array( 285 element_type = rt_type, 286 element_type_template = rt_template, 287 element_type_argument = rt_arg, 288 count1 = attr.arr1, count2 = attr.arr2, 289 parent = self) 290 291 # assign attribute value 292 setattr(self, "_%s_value_" % attr.name, attr_instance) 293 294 # add instance to item list 295 self._items.append(attr_instance)296298 """Copy attributes from a given block (one block class must be a 299 subclass of the other). Returns self.""" 300 # check class lineage 301 if isinstance(self, block.__class__): 302 attrlist = block._get_filtered_attribute_list() 303 elif isinstance(block, self.__class__): 304 attrlist = self._get_filtered_attribute_list() 305 else: 306 raise ValueError("deepcopy: classes %s and %s unrelated" 307 % (self.__class__.__name__, block.__class__.__name__)) 308 # copy the attributes 309 for attr in attrlist: 310 attrvalue = getattr(self, attr.name) 311 if isinstance(attrvalue, StructBase): 312 attrvalue.deepcopy(getattr(block, attr.name)) 313 elif isinstance(attrvalue, Array): 314 attrvalue.update_size() 315 attrvalue.deepcopy(getattr(block, attr.name)) 316 else: 317 setattr(self, attr.name, getattr(block, attr.name)) 318 319 return self320 321 # string of all attributes323 text = '%s instance at 0x%08X\n' % (self.__class__, id(self)) 324 # used to track names of attributes that have already been added 325 # is faster than self.__dict__.has_key(...) 326 for attr in self._get_filtered_attribute_list(): 327 # append string 328 attr_str_lines = str( 329 getattr(self, "_%s_value_" % attr.name)).splitlines() 330 if len(attr_str_lines) > 1: 331 text += '* %s :\n' % attr.name 332 for attr_str in attr_str_lines: 333 text += ' %s\n' % attr_str 334 elif attr_str_lines: 335 text += '* %s : %s\n' % (attr.name, attr_str_lines[0]) 336 else: 337 #print(getattr(self, "_%s_value_" % attr.name)) 338 text += '* %s : <None>\n' % attr.name 339 return text340342 """Read structure from stream.""" 343 # read all attributes 344 for attr in self._get_filtered_attribute_list(data): 345 # skip abstract attributes 346 if attr.is_abstract: 347 continue 348 # get attribute argument (can only be done at runtime) 349 rt_arg = attr.arg if isinstance(attr.arg, (int, long, type(None))) \ 350 else getattr(self, attr.arg) 351 # read the attribute 352 attr_value = getattr(self, "_%s_value_" % attr.name) 353 attr_value.arg = rt_arg 354 attr_value.read(stream, data)355 ### UNCOMMENT FOR DEBUGGING WHILE READING 356 #print("* %s.%s" % (self.__class__.__name__, attr.name)) # debug 357 #val = getattr(self, "_%s_value_" % attr.name) # debug 358 #if isinstance(val, BasicBase): # debug 359 # try: 360 # print(val.get_value()) # debug 361 # except Exception: 362 # pass 363 #else: 364 # print(val.__class__.__name__) 365367 """Write structure to stream.""" 368 # write all attributes 369 for attr in self._get_filtered_attribute_list(data): 370 # skip abstract attributes 371 if attr.is_abstract: 372 continue 373 # get attribute argument (can only be done at runtime) 374 rt_arg = attr.arg if isinstance(attr.arg, (int, long, type(None))) \ 375 else getattr(self, attr.arg) 376 # write the attribute 377 attr_value = getattr(self, "_%s_value_" % attr.name) 378 attr_value.arg = rt_arg 379 getattr(self, "_%s_value_" % attr.name).write(stream, data)380 ### UNCOMMENT FOR DEBUGGING WHILE WRITING 381 #print("* %s.%s" % (self.__class__.__name__, attr.name)) # debug 382 #val = getattr(self, "_%s_value_" % attr.name) # debug 383 #if isinstance(val, BasicBase): # debug 384 # try: 385 # print(val.get_value()) # debug 386 # except Exception: 387 # pass 388 #else: 389 # print(val.__class__.__name__) 390392 """Fix links in the structure.""" 393 # parse arguments 394 # fix links in all attributes 395 for attr in self._get_filtered_attribute_list(data): 396 # check if there are any links at all 397 # (commonly this speeds things up considerably) 398 if not attr.type_._has_links: 399 continue 400 #print("fixlinks %s" % attr.name) 401 # fix the links in the attribute 402 getattr(self, "_%s_value_" % attr.name).fix_links(data)403405 """Get list of all links in the structure.""" 406 # get all links 407 links = [] 408 for attr in self._get_filtered_attribute_list(data): 409 # check if there are any links at all 410 # (this speeds things up considerably) 411 if not attr.type_._has_links: 412 continue 413 # extend list of links 414 links.extend( 415 getattr(self, "_" + attr.name + "_value_").get_links(data)) 416 # return the list of all links in all attributes 417 return links418420 """Get list of all strings in the structure.""" 421 # get all strings 422 strings = [] 423 for attr in self._get_filtered_attribute_list(data): 424 # check if there are any strings at all 425 # (this speeds things up considerably) 426 if (not attr.type_ is type(None)) and (not attr.type_._has_strings): 427 continue 428 # extend list of strings 429 strings.extend( 430 getattr(self, "_%s_value_" % attr.name).get_strings(data)) 431 # return the list of all strings in all attributes 432 return strings433435 """Get list of all references in the structure. Refs are 436 links that point down the tree. For instance, if you need to parse 437 the whole tree starting from the root you would use get_refs and not 438 get_links, as get_links could result in infinite recursion.""" 439 # get all refs 440 refs = [] 441 for attr in self._get_filtered_attribute_list(data): 442 # check if there are any links at all 443 # (this speeds things up considerably) 444 if not attr.type_._has_links: 445 continue 446 # extend list of refs 447 refs.extend( 448 getattr(self, "_%s_value_" % attr.name).get_refs(data)) 449 # return the list of all refs in all attributes 450 return refs451453 """Calculate the structure size in bytes.""" 454 # calculate size 455 size = 0 456 for attr in self._get_filtered_attribute_list(data): 457 # skip abstract attributes 458 if attr.is_abstract: 459 continue 460 size += getattr(self, "_%s_value_" % attr.name).get_size(data) 461 return size462464 """Calculate a hash for the structure, as a tuple.""" 465 # calculate hash 466 hsh = [] 467 for attr in self._get_filtered_attribute_list(data): 468 hsh.append( 469 getattr(self, "_%s_value_" % attr.name).get_hash(data)) 470 return tuple(hsh)471473 for attr in self._get_filtered_attribute_list(): 474 # check if there are any links at all 475 # (this speeds things up considerably) 476 if not attr.type_._has_links: 477 continue 478 getattr(self, "_%s_value_" % attr.name).replace_global_node( 479 oldbranch, newbranch, **kwargs)480 481 @classmethod 485 486 @classmethod 490 491 @classmethod493 """Calculate the list of all attributes of this structure.""" 494 # string of attributes of base classes of cls 495 attrs = [] 496 for base in cls.__bases__: 497 try: 498 attrs.extend(base._get_attribute_list()) 499 except AttributeError: # when base class is "object" 500 pass 501 attrs.extend(cls._attrs) 502 return attrs503 504 @classmethod506 """Calculate the list of all attributes names in this structure. 507 Skips duplicate names.""" 508 # string of attributes of base classes of cls 509 names = [] 510 for base in cls.__bases__: 511 try: 512 names.extend(base._get_names()) 513 except AttributeError: # when base class is "object" 514 pass 515 for attr in cls._attrs: 516 if attr.name in names: 517 continue 518 else: 519 names.append(attr.name) 520 return names521523 """Generator for listing all 'active' attributes, that is, 524 attributes whose condition evaluates ``True``, whose version 525 interval contains C{version}, and whose user version is 526 C{user_version}. ``None`` for C{version} or C{user_version} means 527 that these checks are ignored. Duplicate names are skipped as 528 well. 529 530 Note: version and user_version arguments are deprecated, use 531 the data argument instead. 532 """ 533 if data is not None: 534 version = data.version 535 user_version = data.user_version 536 else: 537 version = None 538 user_version = None 539 names = [] 540 for attr in self._attribute_list: 541 #print(attr.name, version, attr.ver1, attr.ver2) # debug 542 543 # check version 544 if not (version is None): 545 if (not (attr.ver1 is None)) and version < attr.ver1: 546 continue 547 if (not (attr.ver2 is None)) and version > attr.ver2: 548 continue 549 #print("version check passed") # debug 550 551 # check user version 552 if not(attr.userver is None or user_version is None) \ 553 and user_version != attr.userver: 554 continue 555 #print("user version check passed") # debug 556 557 # check conditions 558 if not (attr.cond is None) and not attr.cond.eval(self): 559 continue 560 561 if not(version is None or user_version is None 562 or attr.vercond is None): 563 if not attr.vercond.eval(data): 564 continue 565 566 #print("condition passed") # debug 567 568 # skip dupiclate names 569 if attr.name in names: 570 continue 571 #print("duplicate check passed") # debug 572 573 names.append(attr.name) 574 # passed all tests 575 # so yield the attribute 576 yield attr577 581 582 # important note: to apply partial(set_attribute, name = 'xyz') the 583 # name argument must be last585 """Set a (non-basic) attribute.""" 586 # check class 587 attr = getattr(self, "_" + name + "_value_") 588 if attr.__class__ is not value.__class__: 589 raise TypeError("expected %s but got %s" 590 % (attr.__class__.__name__, 591 value.__class__.__name__)) 592 # set it 593 setattr(self, "_" + name + "_value_", value)594 598 599 # important note: to apply partial(set_attribute, name = 'xyz') the 600 # name argument must be last602 """Set the value of a basic attribute.""" 603 getattr(self, "_" + name + "_value_").set_value(value)604606 """Get a template attribute.""" 607 try: 608 return self.get_basic_attribute(name) 609 except AttributeError: 610 return self.get_attribute(name)611 612 # important note: to apply partial(set_attribute, name = 'xyz') the 613 # name argument must be last615 """Set the value of a template attribute.""" 616 try: 617 self.set_basic_attribute(value, name) 618 except AttributeError: 619 self.set_attribute(value, name)620622 """A generator for parsing all blocks in the tree (starting from and 623 including C{self}). By default, there is no tree structure, so returns 624 self.""" 625 # return self 626 yield self627 628 # DetailNode 629 633 637 638 639 # GlobalNode 640642 """Construct a convenient name for the block itself.""" 643 return (pyffi.object_models.common._as_str(self.name) 644 if hasattr(self, "name") else "")645647 # TODO replace get_refs with a generator as well 648 for branch in self.get_refs(): 649 yield branch
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Mon Oct 10 19:04:14 2011 | http://epydoc.sourceforge.net |