lib: Utilize smaps in the idl.
[sliver-openvswitch.git] / python / ovs / db / types.py
index 2ca0d29..5865acd 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2009, 2010, 2011 Nicira Networks
+# Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # limitations under the License.
 
 import sys
+import uuid
 
 from ovs.db import error
 import ovs.db.parser
 import ovs.db.data
 import ovs.ovsuuid
 
+
 class AtomicType(object):
-    def __init__(self, name, default):
+    def __init__(self, name, default, python_types):
         self.name = name
         self.default = default
+        self.python_types = python_types
 
     @staticmethod
     def from_string(s):
@@ -30,16 +33,14 @@ class AtomicType(object):
             for atomic_type in ATOMIC_TYPES:
                 if s == atomic_type.name:
                     return atomic_type
-        raise error.Error("\"%s\" is not an atomic type" % s)
+        raise error.Error('"%s" is not an atomic-type' % s, s)
 
     @staticmethod
     def from_json(json):
         if type(json) not in [str, unicode]:
             raise error.Error("atomic-type expected", json)
-        try:
+        else:
             return AtomicType.from_string(json)
-        except error.Error:
-            raise error.Error("\"%s\" is not an atomic-type" % json, json)
 
     def __str__(self):
         return self.name
@@ -53,16 +54,17 @@ class AtomicType(object):
     def default_atom(self):
         return ovs.db.data.Atom(self, self.default)
 
-VoidType = AtomicType("void", None)
-IntegerType = AtomicType("integer", 0)
-RealType = AtomicType("real", 0.0)
-BooleanType = AtomicType("boolean", False)
-StringType = AtomicType("string", "")
-UuidType = AtomicType("uuid", ovs.ovsuuid.UUID.zero())
+VoidType = AtomicType("void", None, ())
+IntegerType = AtomicType("integer", 0, (int, long))
+RealType = AtomicType("real", 0.0, (int, long, float))
+BooleanType = AtomicType("boolean", False, (bool,))
+StringType = AtomicType("string", "", (str, unicode))
+UuidType = AtomicType("uuid", ovs.ovsuuid.zero(), (uuid.UUID,))
 
 ATOMIC_TYPES = [VoidType, IntegerType, RealType, BooleanType, StringType,
                 UuidType]
 
+
 def escapeCString(src):
     dst = ""
     for c in src:
@@ -89,10 +91,13 @@ def escapeCString(src):
             dst += c
     return dst
 
+
 def commafy(x):
     """Returns integer x formatted in decimal with thousands set off by
     commas."""
     return _commafy("%d" % x)
+
+
 def _commafy(s):
     if s.startswith('-'):
         return '-' + _commafy(s[1:])
@@ -101,12 +106,14 @@ def _commafy(s):
     else:
         return _commafy(s[:-3]) + ',' + _commafy(s[-3:])
 
+
 def returnUnchanged(x):
     return x
 
+
 class BaseType(object):
     def __init__(self, type_, enum=None, min=None, max=None,
-                 min_length = 0, max_length=sys.maxint, ref_table=None):
+                 min_length=0, max_length=sys.maxint, ref_table_name=None):
         assert isinstance(type_, AtomicType)
         self.type = type_
         self.enum = enum
@@ -114,7 +121,12 @@ class BaseType(object):
         self.max = max
         self.min_length = min_length
         self.max_length = max_length
-        self.ref_table = ref_table
+        self.ref_table_name = ref_table_name
+        if ref_table_name:
+            self.ref_type = 'strong'
+        else:
+            self.ref_type = None
+        self.ref_table = None
 
     def default(self):
         return ovs.db.data.Atom.default(self.type)
@@ -126,7 +138,7 @@ class BaseType(object):
                 self.min == other.min and self.max == other.max and
                 self.min_length == other.min_length and
                 self.max_length == other.max_length and
-                self.ref_table == other.ref_table)
+                self.ref_table_name == other.ref_table_name)
 
     def __ne__(self, other):
         if not isinstance(other, BaseType):
@@ -140,15 +152,15 @@ class BaseType(object):
         if value is None:
             value = default
         else:
-            max_value = 2**32 - 1
-            if value < 0 or value > max_value:
+            max_value = 2 ** 32 - 1
+            if not (0 <= value <= max_value):
                 raise error.Error("%s out of valid range 0 to %d"
                                   % (name, max_value), value)
         return value
 
     @staticmethod
     def from_json(json):
-        if type(json) == unicode:
+        if type(json) in [str, unicode]:
             return BaseType(AtomicType.from_json(json))
 
         parser = ovs.db.parser.Parser(json, "ovsdb type")
@@ -158,16 +170,19 @@ class BaseType(object):
 
         enum = parser.get_optional("enum", [])
         if enum is not None:
-            base.enum = ovs.db.data.Datum.from_json(BaseType.get_enum_type(base.type), enum)
+            base.enum = ovs.db.data.Datum.from_json(
+                    BaseType.get_enum_type(base.type), enum)
         elif base.type == IntegerType:
             base.min = parser.get_optional("minInteger", [int, long])
             base.max = parser.get_optional("maxInteger", [int, long])
-            if base.min is not None and base.max is not None and base.min > base.max:
+            if (base.min is not None and base.max is not None
+                    and base.min > base.max):
                 raise error.Error("minInteger exceeds maxInteger", json)
         elif base.type == RealType:
             base.min = parser.get_optional("minReal", [int, long, float])
             base.max = parser.get_optional("maxReal", [int, long, float])
-            if base.min is not None and base.max is not None and base.min > base.max:
+            if (base.min is not None and base.max is not None
+                    and base.min > base.max):
                 raise error.Error("minReal exceeds maxReal", json)
         elif base.type == StringType:
             base.min_length = BaseType.__parse_uint(parser, "minLength", 0)
@@ -176,13 +191,13 @@ class BaseType(object):
             if base.min_length > base.max_length:
                 raise error.Error("minLength exceeds maxLength", json)
         elif base.type == UuidType:
-            base.ref_table = parser.get_optional("refTable", ['id'])
-            if base.ref_table:
+            base.ref_table_name = parser.get_optional("refTable", ['id'])
+            if base.ref_table_name:
                 base.ref_type = parser.get_optional("refType", [str, unicode],
                                                    "strong")
                 if base.ref_type not in ['strong', 'weak']:
-                    raise error.Error("refType must be \"strong\" or \"weak\" "
-                                      "(not \"%s\")" % base.ref_type)
+                    raise error.Error('refType must be "strong" or "weak" '
+                                      '(not "%s")' % base.ref_type)
         parser.finish()
 
         return base
@@ -212,15 +227,17 @@ class BaseType(object):
             if self.max_length != sys.maxint:
                 json['maxLength'] = self.max_length
         elif self.type == UuidType:
-            if self.ref_table:
-                json['refTable'] = self.ref_table
+            if self.ref_table_name:
+                json['refTable'] = self.ref_table_name
                 if self.ref_type != 'strong':
                     json['refType'] = self.ref_type
         return json
 
-    def clone(self):
-        return BaseType(self.type, self.enum.clone(), self.min, self.max,
-                        self.min_length, self.max_length, self.ref_table)
+    def copy(self):
+        base = BaseType(self.type, self.enum.copy(), self.min, self.max,
+                        self.min_length, self.max_length, self.ref_table_name)
+        base.ref_table = self.ref_table
+        return base
 
     def is_valid(self):
         if self.type in (VoidType, BooleanType, UuidType):
@@ -233,9 +250,10 @@ class BaseType(object):
             return False
 
     def has_constraints(self):
-        return (self.enum is not None or self.min is not None or self.max is not None or
+        return (self.enum is not None or self.min is not None or
+                self.max is not None or
                 self.min_length != 0 or self.max_length != sys.maxint or
-                self.ref_table is not None)
+                self.ref_table_name is not None)
 
     def without_constraints(self):
         return BaseType(self.type)
@@ -245,9 +263,9 @@ class BaseType(object):
         """Returns the type of the 'enum' member for a BaseType whose
         'type' is 'atomic_type'."""
         return Type(BaseType(atomic_type), None, 1, sys.maxint)
-    
+
     def is_ref(self):
-        return self.type == UuidType and self.ref_table is not None
+        return self.type == UuidType and self.ref_table_name is not None
 
     def is_strong_ref(self):
         return self.is_ref() and self.ref_type == 'strong'
@@ -256,55 +274,64 @@ class BaseType(object):
         return self.is_ref() and self.ref_type == 'weak'
 
     def toEnglish(self, escapeLiteral=returnUnchanged):
-        if self.type == UuidType and self.ref_table:
-            s = escapeLiteral(self.ref_table)
+        if self.type == UuidType and self.ref_table_name:
+            s = escapeLiteral(self.ref_table_name)
             if self.ref_type == 'weak':
                 s = "weak reference to " + s
             return s
         else:
             return self.type.to_string()
 
-    def constraintsToEnglish(self, escapeLiteral=returnUnchanged):
+    def constraintsToEnglish(self, escapeLiteral=returnUnchanged,
+                             escapeNumber=returnUnchanged):
         if self.enum:
             literals = [value.toEnglish(escapeLiteral)
                         for value in self.enum.values]
             if len(literals) == 2:
-                return 'either %s or %s' % (literals[0], literals[1])
+                english = 'either %s or %s' % (literals[0], literals[1])
             else:
-                return 'one of %s, %s, or %s' % (literals[0],
-                                                 ', '.join(literals[1:-1]),
-                                                 literals[-1])
+                english = 'one of %s, %s, or %s' % (literals[0],
+                                                    ', '.join(literals[1:-1]),
+                                                    literals[-1])
         elif self.min is not None and self.max is not None:
             if self.type == IntegerType:
-                return 'in range %s to %s' % (commafy(self.min),
-                                              commafy(self.max))
+                english = 'in range %s to %s' % (
+                    escapeNumber(commafy(self.min)),
+                    escapeNumber(commafy(self.max)))
             else:
-                return 'in range %g to %g' % (self.min, self.max)
+                english = 'in range %s to %s' % (
+                    escapeNumber("%g" % self.min),
+                    escapeNumber("%g" % self.max))
         elif self.min is not None:
             if self.type == IntegerType:
-                return 'at least %s' % commafy(self.min)
+                english = 'at least %s' % escapeNumber(commafy(self.min))
             else:
-                return 'at least %g' % self.min
+                english = 'at least %s' % escapeNumber("%g" % self.min)
         elif self.max is not None:
             if self.type == IntegerType:
-                return 'at most %s' % commafy(self.max)
+                english = 'at most %s' % escapeNumber(commafy(self.max))
             else:
-                return 'at most %g' % self.max
+                english = 'at most %s' % escapeNumber("%g" % self.max)
         elif self.min_length != 0 and self.max_length != sys.maxint:
             if self.min_length == self.max_length:
-                return 'exactly %d characters long' % (self.min_length)
+                english = ('exactly %s characters long'
+                           % commafy(self.min_length))
             else:
-                return 'between %d and %d characters long' % (self.min_length, self.max_length)
+                english = ('between %s and %s characters long'
+                        % (commafy(self.min_length),
+                           commafy(self.max_length)))
         elif self.min_length != 0:
-            return 'at least %d characters long' % self.min_length
+            return 'at least %s characters long' % commafy(self.min_length)
         elif self.max_length != sys.maxint:
-            return 'at most %d characters long' % self.max_length
+            english = 'at most %s characters long' % commafy(self.max_length)
         else:
-            return ''
+            english = ''
+
+        return english
 
     def toCType(self, prefix):
-        if self.ref_table:
-            return "struct %s%s *" % (prefix, self.ref_table.lower())
+        if self.ref_table_name:
+            return "struct %s%s *" % (prefix, self.ref_table_name.lower())
         else:
             return {IntegerType: 'int64_t ',
                     RealType: 'double ',
@@ -317,7 +344,7 @@ class BaseType(object):
 
     def copyCValue(self, dst, src):
         args = {'dst': dst, 'src': src}
-        if self.ref_table:
+        if self.ref_table_name:
             return ("%(dst)s = %(src)s->header_.uuid;") % args
         elif self.type == StringType:
             return "%(dst)s = xstrdup(%(src)s);" % args
@@ -325,10 +352,10 @@ class BaseType(object):
             return "%(dst)s = %(src)s;" % args
 
     def initCDefault(self, var, is_optional):
-        if self.ref_table:
+        if self.ref_table_name:
             return "%s = NULL;" % var
         elif self.type == StringType and not is_optional:
-            return "%s = \"\";" % var
+            return '%s = "";' % var
         else:
             pattern = {IntegerType: '%s = 0;',
                        RealType: '%s = 0.0;',
@@ -336,20 +363,22 @@ class BaseType(object):
                        BooleanType: '%s = false;',
                        StringType: '%s = NULL;'}[self.type]
             return pattern % var
-            
+
     def cInitBaseType(self, indent, var):
         stmts = []
-        stmts.append('ovsdb_base_type_init(&%s, OVSDB_TYPE_%s);' % (
-                var, self.type.to_string().upper()),)
+        stmts.append('ovsdb_base_type_init(&%s, %s);' % (
+                var, self.toAtomicType()))
         if self.enum:
             stmts.append("%s.enum_ = xmalloc(sizeof *%s.enum_);"
                          % (var, var))
             stmts += self.enum.cInitDatum("%s.enum_" % var)
         if self.type == IntegerType:
             if self.min is not None:
-                stmts.append('%s.u.integer.min = INT64_C(%d);' % (var, self.min))
+                stmts.append('%s.u.integer.min = INT64_C(%d);'
+                        % (var, self.min))
             if self.max is not None:
-                stmts.append('%s.u.integer.max = INT64_C(%d);' % (var, self.max))
+                stmts.append('%s.u.integer.max = INT64_C(%d);'
+                        % (var, self.max))
         elif self.type == RealType:
             if self.min is not None:
                 stmts.append('%s.u.real.min = %d;' % (var, self.min))
@@ -357,28 +386,36 @@ class BaseType(object):
                 stmts.append('%s.u.real.max = %d;' % (var, self.max))
         elif self.type == StringType:
             if self.min_length is not None:
-                stmts.append('%s.u.string.minLen = %d;' % (var, self.min_length))            
+                stmts.append('%s.u.string.minLen = %d;'
+                        % (var, self.min_length))
             if self.max_length != sys.maxint:
-                stmts.append('%s.u.string.maxLen = %d;' % (var, self.max_length))
+                stmts.append('%s.u.string.maxLen = %d;'
+                        % (var, self.max_length))
         elif self.type == UuidType:
-            if self.ref_table is not None:
-                stmts.append('%s.u.uuid.refTableName = "%s";' % (var, escapeCString(self.ref_table)))
-                stmts.append('%s.u.uuid.refType = OVSDB_REF_%s;' % (var, self.ref_type.upper()))
+            if self.ref_table_name is not None:
+                stmts.append('%s.u.uuid.refTableName = "%s";'
+                        % (var, escapeCString(self.ref_table_name)))
+                stmts.append('%s.u.uuid.refType = OVSDB_REF_%s;'
+                        % (var, self.ref_type.upper()))
         return '\n'.join([indent + stmt for stmt in stmts])
 
+
 class Type(object):
-    def __init__(self, key, value=None, n_min=1, n_max=1):
+    DEFAULT_MIN = 1
+    DEFAULT_MAX = 1
+
+    def __init__(self, key, value=None, n_min=DEFAULT_MIN, n_max=DEFAULT_MAX):
         self.key = key
         self.value = value
         self.n_min = n_min
         self.n_max = n_max
 
-    def clone(self):
+    def copy(self):
         if self.value is None:
             value = None
         else:
-            value = self.value.clone()
-        return Type(self.key.clone(), value, self.n_min, self.n_max)
+            value = self.value.copy()
+        return Type(self.key.copy(), value, self.n_min, self.n_max)
 
     def __eq__(self, other):
         if not isinstance(other, Type):
@@ -396,9 +433,7 @@ class Type(object):
         return (self.key.type != VoidType and self.key.is_valid() and
                 (self.value is None or
                  (self.value.type != VoidType and self.value.is_valid())) and
-                self.n_min <= 1 and
-                self.n_min <= self.n_max and
-                self.n_max >= 1)
+                self.n_min <= 1 <= self.n_max)
 
     def is_scalar(self):
         return self.n_min == 1 and self.n_max == 1 and not self.value
@@ -415,27 +450,32 @@ class Type(object):
     def is_map(self):
         return self.value is not None
 
+    def is_smap(self):
+        return (self.is_map()
+                and self.key.type == StringType
+                and self.value.type == StringType)
+
     def is_optional_pointer(self):
         return (self.is_optional() and not self.value
-                and (self.key.type == StringType or self.key.ref_table))
+                and (self.key.type == StringType or self.key.ref_table_name))
 
     @staticmethod
     def __n_from_json(json, default):
         if json is None:
             return default
-        elif type(json) == int and json >= 0 and json <= sys.maxint:
+        elif type(json) == int and 0 <= json <= sys.maxint:
             return json
         else:
             raise error.Error("bad min or max value", json)
-    
+
     @staticmethod
     def from_json(json):
         if type(json) in [str, unicode]:
             return Type(BaseType.from_json(json))
 
         parser = ovs.db.parser.Parser(json, "ovsdb type")
-        key_json = parser.get("key", [dict, unicode])
-        value_json = parser.get_optional("value", [dict, unicode])
+        key_json = parser.get("key", [dict, str, unicode])
+        value_json = parser.get_optional("value", [dict, str, unicode])
         min_json = parser.get_optional("min", [int])
         max_json = parser.get_optional("max", [int, str, unicode])
         parser.finish()
@@ -446,12 +486,12 @@ class Type(object):
         else:
             value = None
 
-        n_min = Type.__n_from_json(min_json, 1)
+        n_min = Type.__n_from_json(min_json, Type.DEFAULT_MIN)
 
         if max_json == 'unlimited':
             n_max = sys.maxint
         else:
-            n_max = Type.__n_from_json(max_json, 1)            
+            n_max = Type.__n_from_json(max_json, Type.DEFAULT_MAX)
 
         type_ = Type(key, value, n_min, n_max)
         if not type_.is_valid():
@@ -465,11 +505,11 @@ class Type(object):
         json = {"key": self.key.to_json()}
         if self.value is not None:
             json["value"] = self.value.to_json()
-        if self.n_min != 1:
+        if self.n_min != Type.DEFAULT_MIN:
             json["min"] = self.n_min
         if self.n_max == sys.maxint:
             json["max"] = "unlimited"
-        elif self.n_max != 1:
+        elif self.n_max != Type.DEFAULT_MAX:
             json["max"] = self.n_max
         return json
 
@@ -488,13 +528,14 @@ class Type(object):
         else:
             if self.n_max == sys.maxint:
                 if self.n_min:
-                    quantity = "%d or more " % self.n_min
+                    quantity = "%s or more " % commafy(self.n_min)
                 else:
                     quantity = ""
             elif self.n_min:
-                quantity = "%d to %d " % (self.n_min, self.n_max)
+                quantity = "%s to %s " % (commafy(self.n_min),
+                                          commafy(self.n_max))
             else:
-                quantity = "up to %d " % self.n_max
+                quantity = "up to %s " % commafy(self.n_max)
 
             if self.value:
                 return "map of %s%s-%s pairs" % (quantity, keyName, valueName)
@@ -505,24 +546,25 @@ class Type(object):
                     plural = keyName + "s"
                 return "set of %s%s" % (quantity, plural)
 
-    def constraintsToEnglish(self, escapeLiteral=returnUnchanged):
-        s = ""
-
+    def constraintsToEnglish(self, escapeLiteral=returnUnchanged,
+                             escapeNumber=returnUnchanged):
         constraints = []
-        keyConstraints = self.key.constraintsToEnglish(escapeLiteral)
+        keyConstraints = self.key.constraintsToEnglish(escapeLiteral,
+                                                       escapeNumber)
         if keyConstraints:
             if self.value:
-                constraints += ['key ' + keyConstraints]
+                constraints.append('key %s' % keyConstraints)
             else:
-                constraints += [keyConstraints]
+                constraints.append(keyConstraints)
 
         if self.value:
-            valueConstraints = self.value.constraintsToEnglish(escapeLiteral)
+            valueConstraints = self.value.constraintsToEnglish(escapeLiteral,
+                                                               escapeNumber)
             if valueConstraints:
-                constraints += ['value ' + valueConstraints]
+                constraints.append('value %s' % valueConstraints)
 
         return ', '.join(constraints)
-                
+
     def cDeclComment(self):
         if self.n_min == 1 and self.n_max == 1 and self.key.type == StringType:
             return "\t/* Always nonnull. */"
@@ -538,9 +580,8 @@ class Type(object):
                          'OVSDB_TYPE_VOID);' % (indent, var))
         initMin = "%s%s.n_min = %s;" % (indent, var, self.n_min)
         if self.n_max == sys.maxint:
-            max = "UINT_MAX"
+            n_max = "UINT_MAX"
         else:
-            max = self.n_max
-        initMax = "%s%s.n_max = %s;" % (indent, var, max)
+            n_max = self.n_max
+        initMax = "%s%s.n_max = %s;" % (indent, var, n_max)
         return "\n".join((initKey, initValue, initMin, initMax))
-