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