Home | History | Annotate | Download | only in server2
      1 # Copyright 2013 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 import json
      6 from collections import Iterable, Mapping
      7 
      8 class LookupResult(object):
      9   '''Returned from APISchemaGraph.Lookup(), and relays whether or not
     10   some element was found and what annotation object was associated with it,
     11   if any.
     12   '''
     13 
     14   def __init__(self, found=None, annotation=None):
     15     assert found is not None, 'LookupResult was given None value for |found|.'
     16     self.found = found
     17     self.annotation = annotation
     18 
     19   def __eq__(self, other):
     20     return self.__dict__ == other.__dict__
     21 
     22   def __ne__(self, other):
     23     return not (self == other)
     24 
     25   def __repr__(self):
     26     return '%s%s' % (type(self).__name__, repr(self.__dict__))
     27 
     28   def __str__(self):
     29     return repr(self)
     30 
     31 
     32 class _GraphNode(dict):
     33   '''Represents some element of an API schema, and allows extra information
     34   about that element to be stored on the |_annotation| object.
     35   '''
     36 
     37   def __init__(self, *args, **kwargs):
     38     # Use **kwargs here since Python is picky with ordering of default args
     39     # and variadic args in the method signature. The only keyword arg we care
     40     # about here is 'annotation'. Intentionally don't pass |**kwargs| into the
     41     # superclass' __init__().
     42     dict.__init__(self, *args)
     43     self._annotation = kwargs.get('annotation')
     44 
     45   def __eq__(self, other):
     46     # _GraphNode inherits __eq__() from dict, which will not take annotation
     47     # objects into account when comparing.
     48     return dict.__eq__(self, other)
     49 
     50   def __ne__(self, other):
     51     return not (self == other)
     52 
     53 
     54 def _NameForNode(node):
     55   '''Creates a unique id for an object in an API schema, depending on
     56   what type of attribute the object is a member of.
     57   '''
     58   if 'namespace' in node: return node['namespace']
     59   if 'name' in node: return node['name']
     60   if 'id' in node: return node['id']
     61   if 'type' in node: return node['type']
     62   if '$ref' in node: return node['$ref']
     63   assert False, 'Problems with naming node: %s' % json.dumps(node, indent=3)
     64 
     65 
     66 def _IsObjectList(value):
     67   '''Determines whether or not |value| is a list made up entirely of
     68   dict-like objects.
     69   '''
     70   return (isinstance(value, Iterable) and
     71           all(isinstance(node, Mapping) for node in value))
     72 
     73 
     74 def _CreateGraph(root):
     75   '''Recursively moves through an API schema, replacing lists of objects
     76   and non-object values with objects.
     77   '''
     78   schema_graph = _GraphNode()
     79   if _IsObjectList(root):
     80     for node in root:
     81       name = _NameForNode(node)
     82       assert name not in schema_graph, 'Duplicate name in API schema graph.'
     83       schema_graph[name] = _GraphNode((key, _CreateGraph(value)) for
     84                                       key, value in node.iteritems())
     85 
     86   elif isinstance(root, Mapping):
     87     for name, node in root.iteritems():
     88       if not isinstance(node, Mapping):
     89         schema_graph[name] = _GraphNode()
     90       else:
     91         schema_graph[name] = _GraphNode((key, _CreateGraph(value)) for
     92                                         key, value in node.iteritems())
     93   return schema_graph
     94 
     95 
     96 def _Subtract(minuend, subtrahend):
     97   ''' A Set Difference adaptation for graphs. Returns a |difference|,
     98   which contains key-value pairs found in |minuend| but not in
     99   |subtrahend|.
    100   '''
    101   difference = _GraphNode()
    102   for key in minuend:
    103     if key not in subtrahend:
    104       # Record all of this key's children as being part of the difference.
    105       difference[key] = _Subtract(minuend[key], {})
    106     else:
    107       # Note that |minuend| and |subtrahend| are assumed to be graphs, and
    108       # therefore should have no lists present, only keys and nodes.
    109       rest = _Subtract(minuend[key], subtrahend[key])
    110       if rest:
    111         # Record a difference if children of this key differed at some point.
    112         difference[key] = rest
    113   return difference
    114 
    115 
    116 def _Update(base, addend, annotation=None):
    117   '''A Set Union adaptation for graphs. Returns a graph which contains
    118   the key-value pairs from |base| combined with any key-value pairs
    119   from |addend| that are not present in |base|.
    120   '''
    121   for key in addend:
    122     if key not in base:
    123       # Add this key and the rest of its children.
    124       base[key] = _Update(_GraphNode(annotation=annotation),
    125                           addend[key],
    126                           annotation=annotation)
    127     else:
    128       # The key is already in |base|, but check its children.
    129        _Update(base[key], addend[key], annotation=annotation)
    130   return base
    131 
    132 
    133 
    134 class APISchemaGraph(object):
    135   '''Provides an interface for interacting with an API schema graph, a
    136   nested dict structure that allows for simpler lookups of schema data.
    137   '''
    138 
    139   def __init__(self, api_schema=None, _graph=None):
    140     self._graph = _graph if _graph is not None else _CreateGraph(api_schema)
    141 
    142   def __eq__(self, other):
    143     return self._graph == other._graph
    144 
    145   def __ne__(self, other):
    146     return not (self == other)
    147 
    148   def Subtract(self, other):
    149     '''Returns an APISchemaGraph instance representing keys that are in
    150     this graph but not in |other|.
    151     '''
    152     return APISchemaGraph(_graph=_Subtract(self._graph, other._graph))
    153 
    154   def Update(self, other, annotation=None):
    155     '''Modifies this graph by adding keys from |other| that are not
    156     already present in this graph.
    157     '''
    158     _Update(self._graph, other._graph, annotation=annotation)
    159 
    160   def Lookup(self, *path):
    161     '''Given a list of path components, |path|, checks if the
    162     APISchemaGraph instance contains |path|.
    163     '''
    164     node = self._graph
    165     for path_piece in path:
    166       node = node.get(path_piece)
    167       if node is None:
    168         return LookupResult(found=False, annotation=None)
    169     return LookupResult(found=True, annotation=node._annotation)
    170 
    171   def IsEmpty(self):
    172     '''Checks for an empty schema graph.
    173     '''
    174     return not self._graph
    175