| Index: tools/json_schema_compiler/feature_compiler.py
 | 
| diff --git a/tools/json_schema_compiler/feature_compiler.py b/tools/json_schema_compiler/feature_compiler.py
 | 
| index 9e0b8fb6883c2a1116e1936c8568e4cc155780c3..6d31695cbd02c0cd661c686db1198bc6aa2c7b62 100644
 | 
| --- a/tools/json_schema_compiler/feature_compiler.py
 | 
| +++ b/tools/json_schema_compiler/feature_compiler.py
 | 
| @@ -98,10 +98,28 @@ CC_FILE_END = """
 | 
|  #                Feature::BLESSED_WEB_PAGE_CONTEXT et al for contexts. If not
 | 
|  #                specified, defaults to false.
 | 
|  #   'values': A list of all possible allowed values for a given key.
 | 
| +#   'shared': Boolean that, if set, ensures that only one of the associated
 | 
| +#       features has the feature property set. Used primarily for complex
 | 
| +#       features - for simple features, there is always at most one feature
 | 
| +#       setting an option.
 | 
| +#   'feature_reference': Only applicable for strings. An object that, if
 | 
| +#       provided, verifies that the value is set to a name of another feature
 | 
| +#       defined in the same features file. It can following options:
 | 
| +#           'reverse_reference': String that should be equal to another feature
 | 
| +#                option. If set, it verifies that the feature referenced by
 | 
| +#                |feature_reference| value references this feature using
 | 
| +#                |reverse_reference| option.
 | 
|  # If a type definition does not have any restrictions (beyond the type itself),
 | 
|  # an empty definition ({}) is used.
 | 
|  FEATURE_GRAMMAR = (
 | 
|    {
 | 
| +    'alias': {
 | 
| +      unicode: {},
 | 
| +      'feature_reference': {
 | 
| +        'reverse_reference': 'source'
 | 
| +      },
 | 
| +      'shared': True
 | 
| +    },
 | 
|      'blacklist': {
 | 
|        list: {'subtype': unicode}
 | 
|      },
 | 
| @@ -197,6 +215,13 @@ FEATURE_GRAMMAR = (
 | 
|          }
 | 
|        }
 | 
|      },
 | 
| +    'source': {
 | 
| +      unicode: {},
 | 
| +      'feature_reference': {
 | 
| +        'reverse_reference': 'alias'
 | 
| +      },
 | 
| +      'shared': True
 | 
| +    },
 | 
|      'whitelist': {
 | 
|        list: {'subtype': unicode}
 | 
|      },
 | 
| @@ -228,13 +253,26 @@ VALIDATION = ({
 | 
|       'ManifestFeatures must specify at least one extension type'),
 | 
|      (partial(DoesNotHaveProperty, 'contexts'),
 | 
|       'ManifestFeatures do not support contexts.'),
 | 
| +    (partial(DoesNotHaveProperty, 'alias'),
 | 
| +     'ManifestFeatures do not support alias.'),
 | 
| +    (partial(DoesNotHaveProperty, 'source'),
 | 
| +     'ManifestFeatures do not support source.'),
 | 
|    ],
 | 
| -  'BehaviorFeature': [],
 | 
| +  'BehaviorFeature': [
 | 
| +    (partial(DoesNotHaveProperty, 'alias'),
 | 
| +     'BehaviorFeatures do not support alias.'),
 | 
| +    (partial(DoesNotHaveProperty, 'source'),
 | 
| +     'BehaviorFeatures do not support source.'),
 | 
| +   ],
 | 
|    'PermissionFeature': [
 | 
|      (partial(HasProperty, 'extension_types'),
 | 
|       'PermissionFeatures must specify at least one extension type'),
 | 
|      (partial(DoesNotHaveProperty, 'contexts'),
 | 
|       'PermissionFeatures do not support contexts.'),
 | 
| +    (partial(DoesNotHaveProperty, 'alias'),
 | 
| +     'PermissionFeatures do not support alias.'),
 | 
| +    (partial(DoesNotHaveProperty, 'source'),
 | 
| +     'PermissionFeatures do not support source.'),
 | 
|    ],
 | 
|  })
 | 
|  
 | 
| @@ -260,6 +298,8 @@ class Feature(object):
 | 
|      self.has_parent = False
 | 
|      self.errors = []
 | 
|      self.feature_values = {}
 | 
| +    self.shared_values = {}
 | 
| +    self.feature_references = []
 | 
|  
 | 
|    def _GetType(self, value):
 | 
|      """Returns the type of the given value. This can be different than type() if
 | 
| @@ -288,7 +328,7 @@ class Feature(object):
 | 
|                         (self.name, key, error))
 | 
|  
 | 
|    def _GetCheckedValue(self, key, expected_type, expected_values,
 | 
| -                       enum_map, value):
 | 
| +                       enum_map, feature_reference, value):
 | 
|      """Returns a string to be used in the generated C++ code for a given key's
 | 
|      python value, or None if the value is invalid. For example, if the python
 | 
|      value is True, this returns 'true', for a string foo, this returns "foo",
 | 
| @@ -300,6 +340,16 @@ class Feature(object):
 | 
|                         value is allowed.
 | 
|        enum_map: The map from python value -> cpp value for all allowed values,
 | 
|                 or None if no special mapping should be made.
 | 
| +      feature_reference: Object set if the value should represent a feature
 | 
| +          reference. If set, the value is expected to be string referencing
 | 
| +          another feature in the feature file. While it will be checked that the
 | 
| +          value is string, validation that the value references an existing
 | 
| +          feature will have to be postponed until all features are parsed - the
 | 
| +          info needed to verify the key value will be cached in
 | 
| +          |self.feature_references|.
 | 
| +          The object may have "reverse_reference" property set - in that case
 | 
| +          the referenced feature should reference this feature by
 | 
| +          "reverse_reference" property.
 | 
|        value: The value to check.
 | 
|      """
 | 
|      valid = True
 | 
| @@ -312,6 +362,10 @@ class Feature(object):
 | 
|        self._AddKeyError(key, 'Illegal value: "%s"' % value)
 | 
|        valid = False
 | 
|  
 | 
| +    if feature_reference and not t in [str, unicode]:
 | 
| +      self._AddKeyError(key, 'Illegal value for feature reference %s' % value)
 | 
| +      valid = False
 | 
| +
 | 
|      if not valid:
 | 
|        return None
 | 
|  
 | 
| @@ -319,6 +373,11 @@ class Feature(object):
 | 
|        return enum_map[value]
 | 
|  
 | 
|      if t in [str, unicode]:
 | 
| +      if feature_reference:
 | 
| +        self.feature_references.append({
 | 
| +          'value': str(value),
 | 
| +          'key': key,
 | 
| +          'reverse_reference': feature_reference.get('reverse_reference')})
 | 
|        return '"%s"' % str(value)
 | 
|      if t is int:
 | 
|        return str(value)
 | 
| @@ -342,6 +401,10 @@ class Feature(object):
 | 
|        v = []
 | 
|        is_all = True
 | 
|  
 | 
| +    if 'shared' in grammar and key in self.shared_values:
 | 
| +      self._AddKeyError(key, 'Key can be set at most once per feature.')
 | 
| +      return
 | 
| +
 | 
|      value_type = self._GetType(v)
 | 
|      if value_type not in grammar:
 | 
|        self._AddKeyError(key, 'Illegal value: "%s"' % v)
 | 
| @@ -363,6 +426,12 @@ class Feature(object):
 | 
|      if value_type is list and 'subtype' in expected:
 | 
|        expected_type = expected['subtype']
 | 
|  
 | 
| +    feature_reference = None
 | 
| +    if 'feature_reference' in grammar:
 | 
| +      feature_reference = {}
 | 
| +      feature_reference['reverse_reference'] = grammar['feature_reference'].get(
 | 
| +          'reverse_reference')
 | 
| +
 | 
|      cpp_value = None
 | 
|      # If this value is a list, iterate over each entry and validate. Otherwise,
 | 
|      # validate the single value.
 | 
| @@ -371,6 +440,7 @@ class Feature(object):
 | 
|        for sub_value in v:
 | 
|          cpp_sub_value = self._GetCheckedValue(key, expected_type,
 | 
|                                                expected_values, enum_map,
 | 
| +                                              feature_reference,
 | 
|                                                sub_value)
 | 
|          if cpp_sub_value:
 | 
|            cpp_value.append(cpp_sub_value)
 | 
| @@ -378,10 +448,13 @@ class Feature(object):
 | 
|          cpp_value = '{' + ','.join(cpp_value) + '}'
 | 
|      else:
 | 
|        cpp_value = self._GetCheckedValue(key, expected_type, expected_values,
 | 
| -                                        enum_map, v)
 | 
| +                                        enum_map, feature_reference, v)
 | 
|  
 | 
|      if cpp_value:
 | 
| -      self.feature_values[key] = cpp_value
 | 
| +      if 'shared' in grammar:
 | 
| +        self.shared_values[key] = cpp_value
 | 
| +      else:
 | 
| +        self.feature_values[key] = cpp_value
 | 
|      elif key in self.feature_values:
 | 
|        # If the key is empty and this feature inherited a value from its parent,
 | 
|        # remove the inherited value.
 | 
| @@ -395,6 +468,9 @@ class Feature(object):
 | 
|      self.feature_values = copy.deepcopy(parent.feature_values)
 | 
|      self.has_parent = True
 | 
|  
 | 
| +  def SetSharedValues(self, values):
 | 
| +    self.shared_values = values
 | 
| +
 | 
|    def Parse(self, parsed_json):
 | 
|      """Parses the feature from the given json value."""
 | 
|      for key in parsed_json.keys():
 | 
| @@ -404,21 +480,111 @@ class Feature(object):
 | 
|        self._ParseKey(key, parsed_json, key_grammar)
 | 
|  
 | 
|    def Validate(self, feature_class):
 | 
| +    feature_values = self.GetAllFeatureValues()
 | 
|      for validator, error in (VALIDATION[feature_class] + VALIDATION['all']):
 | 
| -      if not validator(self.feature_values):
 | 
| +      if not validator(feature_values):
 | 
|          self._AddError(error)
 | 
|  
 | 
| +  def _GetCodeForFeatureValues(self, feature_values):
 | 
| +    """ Gets the Code object for setting feature values for this object. """
 | 
| +    c = Code()
 | 
| +    for key in sorted(feature_values.keys()):
 | 
| +      if key in IGNORED_KEYS:
 | 
| +        continue;
 | 
| +      c.Append('feature->set_%s(%s);' % (key, feature_values[key]))
 | 
| +    return c
 | 
| +
 | 
|    def GetCode(self, feature_class):
 | 
|      """Returns the Code object for generating this feature."""
 | 
|      c = Code()
 | 
|      c.Append('%s* feature = new %s();' % (feature_class, feature_class))
 | 
|      c.Append('feature->set_name("%s");' % self.name)
 | 
| -    for key in sorted(self.feature_values.keys()):
 | 
| -      if key in IGNORED_KEYS:
 | 
| -        continue;
 | 
| -      c.Append('feature->set_%s(%s);' % (key, self.feature_values[key]))
 | 
| +    c.Concat(self._GetCodeForFeatureValues(self.GetAllFeatureValues()))
 | 
|      return c
 | 
|  
 | 
| +  def AsParent(self):
 | 
| +    """ Returns the feature values that should be inherited by children features
 | 
| +    when this feature is set as parent.
 | 
| +    """
 | 
| +    return self
 | 
| +
 | 
| +  def GetAllFeatureValues(self):
 | 
| +    """ Gets all values set for this feature. """
 | 
| +    values = self.feature_values.copy()
 | 
| +    values.update(self.shared_values)
 | 
| +    return values
 | 
| +
 | 
| +  def ValidateFeatureReferences(self, features):
 | 
| +    """ Validates feature values that reference another feature - the validation
 | 
| +    cannot be done when individual features are compiled (like it is done for
 | 
| +    other value types), as whole feature set is not know at that point.
 | 
| +    """
 | 
| +    for ref in self.feature_references:
 | 
| +      # Validate that a feature referenced by this feature exists.
 | 
| +      if not ref['value'] in features:
 | 
| +        self._AddKeyError(ref['key'], '%s is not a feature.' % ref['value'])
 | 
| +        return
 | 
| +
 | 
| +      # Verify that referenced feature references this feature, if reverse
 | 
| +      # reference is required.
 | 
| +      reverse_ref = ref['reverse_reference']
 | 
| +      if reverse_ref:
 | 
| +        referenced_feature_values = features[ref['value']].GetAllFeatureValues()
 | 
| +        reverse_ref_value = referenced_feature_values.get(reverse_ref)
 | 
| +        if (reverse_ref_value != ('"%s"' % self.name)):
 | 
| +          self._AddKeyError(ref['key'],
 | 
| +              'Referenced feature ("%s") should have "%s" set to "%s".' %
 | 
| +                  (ref['value'], reverse_ref, self.name))
 | 
| +
 | 
| +  def GetErrors(self):
 | 
| +    return self.errors;
 | 
| +
 | 
| +class ComplexFeature(Feature):
 | 
| +  """ Complex feature - feature that is comprised of list of features.
 | 
| +  Overall complex feature is available if any of contained
 | 
| +  feature is available.
 | 
| +  """
 | 
| +  def __init__(self, name, shared_values):
 | 
| +    Feature.__init__(self, name)
 | 
| +    self.shared_values = shared_values
 | 
| +    self.feature_list = []
 | 
| +
 | 
| +  def GetCode(self, feature_class):
 | 
| +    c = Code()
 | 
| +    c.Append('std::vector<Feature*> features;')
 | 
| +    for f in self.feature_list:
 | 
| +      c.Sblock('{')
 | 
| +      c.Concat(f.GetCode(feature_class))
 | 
| +      c.Append('features.push_back(feature);')
 | 
| +      c.Eblock('}')
 | 
| +    c.Append('ComplexFeature* feature(new ComplexFeature(&features));')
 | 
| +    c.Append('feature->set_name("%s");' % self.name)
 | 
| +    c.Concat(self._GetCodeForFeatureValues(self.GetAllFeatureValues()))
 | 
| +    return c
 | 
| +
 | 
| +  def AsParent(self):
 | 
| +    for p in self.feature_list:
 | 
| +      if 'default_parent' in p.feature_values:
 | 
| +        parent = p
 | 
| +        break
 | 
| +    assert parent, 'No default parent found for %s' % self.name
 | 
| +    return parent
 | 
| +
 | 
| +  def ValidateFeatureReferences(self, available_features):
 | 
| +    for feature in self.feature_list:
 | 
| +      feature.ValidateFeatureReferences(available_features)
 | 
| +
 | 
| +  def GetAllFeatureValues(self):
 | 
| +    return self.shared_values.copy()
 | 
| +
 | 
| +  def GetErrors(self):
 | 
| +    errors = None
 | 
| +    for feature in self.feature_list:
 | 
| +      if not errors:
 | 
| +        errors = []
 | 
| +      errors.extend(feature.GetErrors())
 | 
| +    return errors
 | 
| +
 | 
|  class FeatureCompiler(object):
 | 
|    """A compiler to load, parse, and generate C++ code for a number of
 | 
|    features.json files."""
 | 
| @@ -486,15 +652,7 @@ class FeatureCompiler(object):
 | 
|        #   raise KeyError('Could not find parent "%s" for feature "%s".' %
 | 
|        #                      (parent_name, feature_name))
 | 
|        return None
 | 
| -    parent_value = self._features[parent_name]
 | 
| -    parent = parent_value
 | 
| -    if type(parent_value) is list:
 | 
| -      for p in parent_value:
 | 
| -        if 'default_parent' in p.feature_values:
 | 
| -          parent = p
 | 
| -          break
 | 
| -      assert parent, 'No default parent found for %s' % parent_name
 | 
| -    return parent
 | 
| +    return self._features[parent_name].AsParent()
 | 
|  
 | 
|    def _CompileFeature(self, feature_name, feature_value):
 | 
|      """Parses a single feature."""
 | 
| @@ -503,11 +661,12 @@ class FeatureCompiler(object):
 | 
|            'nocompile should only be true; otherwise omit this key.')
 | 
|        return
 | 
|  
 | 
| -    def parse_and_validate(name, value, parent):
 | 
| +    def parse_and_validate(name, value, parent, shared_values):
 | 
|        try:
 | 
|          feature = Feature(name)
 | 
|          if parent:
 | 
|            feature.SetParent(parent)
 | 
| +        feature.SetSharedValues(shared_values)
 | 
|          feature.Parse(value)
 | 
|          feature.Validate(self._feature_class)
 | 
|          return feature
 | 
| @@ -516,18 +675,21 @@ class FeatureCompiler(object):
 | 
|          raise
 | 
|  
 | 
|      parent = self._FindParent(feature_name, feature_value)
 | 
| +    shared_values = copy.deepcopy(parent.shared_values) if parent else {}
 | 
| +
 | 
|      # Handle complex features, which are lists of simple features.
 | 
|      if type(feature_value) is list:
 | 
| -      feature_list = []
 | 
| +      feature = ComplexFeature(feature_name, shared_values)
 | 
| +
 | 
|        # This doesn't handle nested complex features. I think that's probably for
 | 
|        # the best.
 | 
|        for v in feature_value:
 | 
| -        feature_list.append(parse_and_validate(feature_name, v, parent))
 | 
| -      self._features[feature_name] = feature_list
 | 
| -      return
 | 
| -
 | 
| -    self._features[feature_name] = parse_and_validate(
 | 
| -                                       feature_name, feature_value, parent)
 | 
| +        feature.feature_list.append(
 | 
| +            parse_and_validate(feature_name, v, parent, feature.shared_values))
 | 
| +      self._features[feature_name] = feature
 | 
| +    else:
 | 
| +      self._features[feature_name] = parse_and_validate(
 | 
| +          feature_name, feature_value, parent, shared_values)
 | 
|  
 | 
|    def Compile(self):
 | 
|      """Parses all features after loading the input files."""
 | 
| @@ -535,6 +697,9 @@ class FeatureCompiler(object):
 | 
|      # Iterate over in sorted order so that parents come first.
 | 
|      for k in sorted(self._json.keys()):
 | 
|        self._CompileFeature(k, self._json[k])
 | 
| +    # Validate values that reference feature
 | 
| +    for key in self._features:
 | 
| +      self._features[key].ValidateFeatureReferences(self._features)
 | 
|  
 | 
|    def Render(self):
 | 
|      """Returns the Code object for the body of the .cc file, which handles the
 | 
| @@ -545,17 +710,7 @@ class FeatureCompiler(object):
 | 
|      for k in sorted(self._features.keys()):
 | 
|        c.Sblock('{')
 | 
|        feature = self._features[k]
 | 
| -      if type(feature) is list:
 | 
| -        c.Append('std::vector<Feature*> features;')
 | 
| -        for f in feature:
 | 
| -          c.Sblock('{')
 | 
| -          c.Concat(f.GetCode(self._feature_class))
 | 
| -          c.Append('features.push_back(feature);')
 | 
| -          c.Eblock('}')
 | 
| -        c.Append('ComplexFeature* feature(new ComplexFeature(&features));')
 | 
| -        c.Append('feature->set_name("%s");' % k)
 | 
| -      else:
 | 
| -        c.Concat(feature.GetCode(self._feature_class))
 | 
| +      c.Concat(feature.GetCode(self._feature_class))
 | 
|        c.Append('AddFeature("%s", feature);' % k)
 | 
|        c.Eblock('}')
 | 
|      c.Eblock('}')
 | 
| 
 |