Home | History | Annotate | Download | only in json_schema_compiler
      1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 from code import Code
      6 from model import Namespace, PropertyType, Type
      7 import cpp_util
      8 from json_parse import OrderedDict
      9 from operator import attrgetter
     10 import schema_util
     11 
     12 class _TypeDependency(object):
     13   """Contains information about a dependency a namespace has on a type: the
     14   type's model, and whether that dependency is "hard" meaning that it cannot be
     15   forward declared.
     16   """
     17   def __init__(self, type_, hard=False):
     18     self.type_ = type_
     19     self.hard = hard
     20 
     21   def GetSortKey(self):
     22     return '%s.%s' % (self.type_.namespace.name, self.type_.name)
     23 
     24 class CppTypeGenerator(object):
     25   """Manages the types of properties and provides utilities for getting the
     26   C++ type out of a model.Property
     27   """
     28   def __init__(self, model, schema_loader, default_namespace=None):
     29     """Creates a cpp_type_generator. The given root_namespace should be of the
     30     format extensions::api::sub. The generator will generate code suitable for
     31     use in the given model's namespace.
     32     """
     33     self._default_namespace = default_namespace
     34     if self._default_namespace is None:
     35       self._default_namespace = model.namespaces.values()[0]
     36     self._schema_loader = schema_loader
     37 
     38   def GetCppNamespaceName(self, namespace):
     39     """Gets the mapped C++ namespace name for the given namespace relative to
     40     the root namespace.
     41     """
     42     return namespace.unix_name
     43 
     44   def GetNamespaceStart(self):
     45     """Get opening self._default_namespace namespace declaration.
     46     """
     47     return Code().Append('namespace %s {' %
     48         self.GetCppNamespaceName(self._default_namespace))
     49 
     50   def GetNamespaceEnd(self):
     51     """Get closing self._default_namespace namespace declaration.
     52     """
     53     return Code().Append('}  // %s' %
     54         self.GetCppNamespaceName(self._default_namespace))
     55 
     56   def GetEnumNoneValue(self, type_):
     57     """Gets the enum value in the given model.Property indicating no value has
     58     been set.
     59     """
     60     return '%s_NONE' % self.FollowRef(type_).unix_name.upper()
     61 
     62   def GetEnumValue(self, type_, enum_value):
     63     """Gets the enum value of the given model.Property of the given type.
     64 
     65     e.g VAR_STRING
     66     """
     67     value = '%s_%s' % (self.FollowRef(type_).unix_name.upper(),
     68                        cpp_util.Classname(enum_value.upper()))
     69     # To avoid collisions with built-in OS_* preprocessor definitions, we add a
     70     # trailing slash to enum names that start with OS_.
     71     if value.startswith("OS_"):
     72       value += "_"
     73     return value
     74 
     75   def GetCppType(self, type_, is_ptr=False, is_in_container=False):
     76     """Translates a model.Property or model.Type into its C++ type.
     77 
     78     If REF types from different namespaces are referenced, will resolve
     79     using self._schema_loader.
     80 
     81     Use |is_ptr| if the type is optional. This will wrap the type in a
     82     scoped_ptr if possible (it is not possible to wrap an enum).
     83 
     84     Use |is_in_container| if the type is appearing in a collection, e.g. a
     85     std::vector or std::map. This will wrap it in the correct type with spacing.
     86     """
     87     cpp_type = None
     88     if type_.property_type == PropertyType.REF:
     89       ref_type = self._FindType(type_.ref_type)
     90       if ref_type is None:
     91         raise KeyError('Cannot find referenced type: %s' % type_.ref_type)
     92       if self._default_namespace is ref_type.namespace:
     93         cpp_type = ref_type.name
     94       else:
     95         cpp_type = '%s::%s' % (ref_type.namespace.unix_name, ref_type.name)
     96     elif type_.property_type == PropertyType.BOOLEAN:
     97       cpp_type = 'bool'
     98     elif type_.property_type == PropertyType.INTEGER:
     99       cpp_type = 'int'
    100     elif type_.property_type == PropertyType.INT64:
    101       cpp_type = 'int64'
    102     elif type_.property_type == PropertyType.DOUBLE:
    103       cpp_type = 'double'
    104     elif type_.property_type == PropertyType.STRING:
    105       cpp_type = 'std::string'
    106     elif type_.property_type == PropertyType.ENUM:
    107       cpp_type = cpp_util.Classname(type_.name)
    108     elif type_.property_type == PropertyType.ANY:
    109       cpp_type = 'base::Value'
    110     elif (type_.property_type == PropertyType.OBJECT or
    111           type_.property_type == PropertyType.CHOICES):
    112       cpp_type = cpp_util.Classname(type_.name)
    113     elif type_.property_type == PropertyType.FUNCTION:
    114       # Functions come into the json schema compiler as empty objects. We can
    115       # record these as empty DictionaryValues so that we know if the function
    116       # was passed in or not.
    117       cpp_type = 'base::DictionaryValue'
    118     elif type_.property_type == PropertyType.ARRAY:
    119       item_cpp_type = self.GetCppType(type_.item_type, is_in_container=True)
    120       cpp_type = 'std::vector<%s>' % cpp_util.PadForGenerics(item_cpp_type)
    121     elif type_.property_type == PropertyType.BINARY:
    122       cpp_type = 'std::string'
    123     else:
    124       raise NotImplementedError('Cannot get type of %s' % type_.property_type)
    125 
    126     # HACK: optional ENUM is represented elsewhere with a _NONE value, so it
    127     # never needs to be wrapped in pointer shenanigans.
    128     # TODO(kalman): change this - but it's an exceedingly far-reaching change.
    129     if not self.FollowRef(type_).property_type == PropertyType.ENUM:
    130       if is_in_container and (is_ptr or not self.IsCopyable(type_)):
    131         cpp_type = 'linked_ptr<%s>' % cpp_util.PadForGenerics(cpp_type)
    132       elif is_ptr:
    133         cpp_type = 'scoped_ptr<%s>' % cpp_util.PadForGenerics(cpp_type)
    134 
    135     return cpp_type
    136 
    137   def IsCopyable(self, type_):
    138     return not (self.FollowRef(type_).property_type in (PropertyType.ANY,
    139                                                         PropertyType.ARRAY,
    140                                                         PropertyType.OBJECT,
    141                                                         PropertyType.CHOICES))
    142 
    143   def GenerateForwardDeclarations(self):
    144     """Returns the forward declarations for self._default_namespace.
    145     """
    146     c = Code()
    147 
    148     for namespace, dependencies in self._NamespaceTypeDependencies().items():
    149       c.Append('namespace %s {' % namespace.unix_name)
    150       for dependency in dependencies:
    151         # No point forward-declaring hard dependencies.
    152         if dependency.hard:
    153           continue
    154         # Add more ways to forward declare things as necessary.
    155         if dependency.type_.property_type in (PropertyType.CHOICES,
    156                                               PropertyType.OBJECT):
    157           c.Append('struct %s;' % dependency.type_.name)
    158       c.Append('}')
    159 
    160     return c
    161 
    162   def GenerateIncludes(self, include_soft=False):
    163     """Returns the #include lines for self._default_namespace.
    164     """
    165     c = Code()
    166     for namespace, dependencies in self._NamespaceTypeDependencies().items():
    167       for dependency in dependencies:
    168         if dependency.hard or include_soft:
    169           c.Append('#include "%s/%s.h"' % (namespace.source_file_dir,
    170                                            namespace.unix_name))
    171     return c
    172 
    173   def _FindType(self, full_name):
    174     """Finds the model.Type with name |qualified_name|. If it's not from
    175     |self._default_namespace| then it needs to be qualified.
    176     """
    177     namespace = self._schema_loader.ResolveType(full_name,
    178                                                 self._default_namespace)
    179     if namespace is None:
    180       raise KeyError('Cannot resolve type %s. Maybe it needs a prefix '
    181                      'if it comes from another namespace?' % full_name)
    182     return namespace.types[schema_util.StripNamespace(full_name)]
    183 
    184   def FollowRef(self, type_):
    185     """Follows $ref link of types to resolve the concrete type a ref refers to.
    186 
    187     If the property passed in is not of type PropertyType.REF, it will be
    188     returned unchanged.
    189     """
    190     if type_.property_type != PropertyType.REF:
    191       return type_
    192     return self.FollowRef(self._FindType(type_.ref_type))
    193 
    194   def _NamespaceTypeDependencies(self):
    195     """Returns a dict ordered by namespace name containing a mapping of
    196     model.Namespace to every _TypeDependency for |self._default_namespace|,
    197     sorted by the type's name.
    198     """
    199     dependencies = set()
    200     for function in self._default_namespace.functions.values():
    201       for param in function.params:
    202         dependencies |= self._TypeDependencies(param.type_,
    203                                                hard=not param.optional)
    204       if function.callback:
    205         for param in function.callback.params:
    206           dependencies |= self._TypeDependencies(param.type_,
    207                                                  hard=not param.optional)
    208     for type_ in self._default_namespace.types.values():
    209       for prop in type_.properties.values():
    210         dependencies |= self._TypeDependencies(prop.type_,
    211                                                hard=not prop.optional)
    212     for event in self._default_namespace.events.values():
    213       for param in event.params:
    214         dependencies |= self._TypeDependencies(param.type_,
    215                                                hard=not param.optional)
    216 
    217     # Make sure that the dependencies are returned in alphabetical order.
    218     dependency_namespaces = OrderedDict()
    219     for dependency in sorted(dependencies, key=_TypeDependency.GetSortKey):
    220       namespace = dependency.type_.namespace
    221       if namespace is self._default_namespace:
    222         continue
    223       if namespace not in dependency_namespaces:
    224         dependency_namespaces[namespace] = []
    225       dependency_namespaces[namespace].append(dependency)
    226 
    227     return dependency_namespaces
    228 
    229   def _TypeDependencies(self, type_, hard=False):
    230     """Gets all the type dependencies of a property.
    231     """
    232     deps = set()
    233     if type_.property_type == PropertyType.REF:
    234       deps.add(_TypeDependency(self._FindType(type_.ref_type), hard=hard))
    235     elif type_.property_type == PropertyType.ARRAY:
    236       # Non-copyable types are not hard because they are wrapped in linked_ptrs
    237       # when generated. Otherwise they're typedefs, so they're hard (though we
    238       # could generate those typedefs in every dependent namespace, but that
    239       # seems weird).
    240       deps = self._TypeDependencies(type_.item_type,
    241                                     hard=self.IsCopyable(type_.item_type))
    242     elif type_.property_type == PropertyType.CHOICES:
    243       for type_ in type_.choices:
    244         deps |= self._TypeDependencies(type_, hard=self.IsCopyable(type_))
    245     elif type_.property_type == PropertyType.OBJECT:
    246       for p in type_.properties.values():
    247         deps |= self._TypeDependencies(p.type_, hard=not p.optional)
    248     return deps
    249 
    250   def GeneratePropertyValues(self, property, line, nodoc=False):
    251     """Generates the Code to display all value-containing properties.
    252     """
    253     c = Code()
    254     if not nodoc:
    255       c.Comment(property.description)
    256 
    257     if property.value is not None:
    258       c.Append(line % {
    259           "type": self.GetCppType(property.type_),
    260           "name": property.name,
    261           "value": property.value
    262         })
    263     else:
    264       has_child_code = False
    265       c.Sblock('namespace %s {' % property.name)
    266       for child_property in property.type_.properties.values():
    267         child_code = self.GeneratePropertyValues(child_property,
    268                                                  line,
    269                                                  nodoc=nodoc)
    270         if child_code:
    271           has_child_code = True
    272           c.Concat(child_code)
    273       c.Eblock('}  // namespace %s' % property.name)
    274       if not has_child_code:
    275         c = None
    276     return c
    277