github.com/Psiphon-Inc/goarista@v0.0.0-20160825065156-d002785f4c67/pathmap/pathmap.go (about)

     1  // Copyright (C) 2016  Arista Networks, Inc.
     2  // Use of this source code is governed by the Apache License 2.0
     3  // that can be found in the COPYING file.
     4  
     5  package pathmap
     6  
     7  // PathMap associates Paths to a values. It allows wildcards. The
     8  // primary use of PathMap is to be able to register handlers to paths
     9  // that can be efficiently looked up every time a path is updated.
    10  //
    11  // For example:
    12  //
    13  // m.Set({"interfaces", "*", "adminStatus"}, AdminStatusHandler)
    14  // m.Set({"interface", "Management1", "adminStatus"}, Management1AdminStatusHandler)
    15  //
    16  // m.Visit({"interfaces", "Ethernet3/32/1", "adminStatus"}, HandlerExecutor)
    17  // >> AdminStatusHandler gets passed to HandlerExecutor
    18  // m.Visit({"interfaces", "Management1", "adminStatus"}, HandlerExecutor)
    19  // >> AdminStatusHandler and Management1AdminStatusHandler gets passed to HandlerExecutor
    20  //
    21  // Note, Visit performance is typically linearly with the length of
    22  // the path. But, it can be as bad as O(2^len(Path)) when TreeMap
    23  // nodes have children and a wildcard associated with it. For example,
    24  // if these paths were registered:
    25  //
    26  // m.Set([]string{"foo", "bar", "baz"}, 1)
    27  // m.Set([]string{"*", "bar", "baz"}, 2)
    28  // m.Set([]string{"*", "*", "baz"}, 3)
    29  // m.Set([]string{"*", "*", "*"}, 4)
    30  // m.Set([]string{"foo", "*", "*"}, 5)
    31  // m.Set([]string{"foo", "bar", "*"}, 6)
    32  // m.Set([]string{"foo", "*", "baz"}, 7)
    33  // m.Set([]string{"*", "bar", "*"}, 8)
    34  //
    35  // m.Visit([]{"foo","bar","baz"}, Foo) // 2^3 nodes traversed
    36  //
    37  // This shouldn't be a concern with our paths because it is likely
    38  // that a TreeMap node will either have a wildcard or children, not
    39  // both. A TreeMap node that corresponds to a collection will often be a
    40  // wildcard, otherwise it will have specific children.
    41  type PathMap interface {
    42  	// Visit calls f for every registration in the PathMap that
    43  	// matches path. For example,
    44  	//
    45  	// m.Set({"foo", "bar"}, 1)
    46  	// m.Set({"*", "bar"}, 2)
    47  	//
    48  	// m.Visit({"foo", "bar"}, Printer)
    49  	// >> Calls Printer(1) and Printer(2)
    50  	Visit(path []string, f VisitorFunc) error
    51  
    52  	// Get returns the mapping for path. This returns the exact
    53  	// mapping for path. For example, if you register two paths
    54  	//
    55  	// m.Set({"foo", "bar"}, 1)
    56  	// m.Set({"*", "bar"}, 2)
    57  	//
    58  	// m.Get({"foo", "bar"}) => 1
    59  	// m.Get({"*", "bar"}) => 2
    60  	Get(path []string) interface{}
    61  
    62  	// Set a mapping of path to value. Path may contain wildcards. Set
    63  	// replaces what was there before.
    64  	Set(path []string, v interface{})
    65  
    66  	// Delete removes the mapping for path
    67  	Delete(path []string) bool
    68  }
    69  
    70  // Wildcard is a special string representing any possible path
    71  const Wildcard string = "*"
    72  
    73  type node struct {
    74  	val      interface{}
    75  	wildcard *node
    76  	children map[string]*node
    77  }
    78  
    79  // New creates a new PathMap
    80  func New() PathMap {
    81  	return &node{}
    82  }
    83  
    84  // VisitorFunc is the func type passed to Visit
    85  type VisitorFunc func(v interface{}) error
    86  
    87  // Visit calls f for every matching registration in the PathMap
    88  func (n *node) Visit(path []string, f VisitorFunc) error {
    89  	for i, element := range path {
    90  		if n.wildcard != nil {
    91  			if err := n.wildcard.Visit(path[i+1:], f); err != nil {
    92  				return err
    93  			}
    94  		}
    95  		next, ok := n.children[element]
    96  		if !ok {
    97  			return nil
    98  		}
    99  		n = next
   100  	}
   101  	if n.val == nil {
   102  		return nil
   103  	}
   104  	return f(n.val)
   105  }
   106  
   107  // Get returns the mapping for path
   108  func (n *node) Get(path []string) interface{} {
   109  	for _, element := range path {
   110  		if element == Wildcard {
   111  			if n.wildcard == nil {
   112  				return nil
   113  			}
   114  			n = n.wildcard
   115  			continue
   116  		}
   117  		next, ok := n.children[element]
   118  		if !ok {
   119  			return nil
   120  		}
   121  		n = next
   122  	}
   123  	return n.val
   124  }
   125  
   126  // Set a mapping of path to value. Path may contain wildcards. Set
   127  // replaces what was there before.
   128  func (n *node) Set(path []string, v interface{}) {
   129  	for _, element := range path {
   130  		if element == Wildcard {
   131  			if n.wildcard == nil {
   132  				n.wildcard = &node{}
   133  			}
   134  			n = n.wildcard
   135  			continue
   136  		}
   137  		if n.children == nil {
   138  			n.children = map[string]*node{}
   139  		}
   140  		next, ok := n.children[element]
   141  		if !ok {
   142  			next = &node{}
   143  			n.children[element] = next
   144  		}
   145  		n = next
   146  	}
   147  	n.val = v
   148  }
   149  
   150  // Delete removes the mapping for path
   151  func (n *node) Delete(path []string) bool {
   152  	nodes := make([]*node, len(path)+1)
   153  	for i, element := range path {
   154  		nodes[i] = n
   155  		if element == Wildcard {
   156  			if n.wildcard == nil {
   157  				return false
   158  			}
   159  			n = n.wildcard
   160  			continue
   161  		}
   162  		next, ok := n.children[element]
   163  		if !ok {
   164  			return false
   165  		}
   166  		n = next
   167  	}
   168  	n.val = nil
   169  	nodes[len(path)] = n
   170  
   171  	// See if we can delete any node objects
   172  	for i := len(path); i > 0; i-- {
   173  		n = nodes[i]
   174  		if n.val != nil || n.wildcard != nil || len(n.children) > 0 {
   175  			break
   176  		}
   177  		parent := nodes[i-1]
   178  		element := path[i-1]
   179  		if element == Wildcard {
   180  			parent.wildcard = nil
   181  		} else {
   182  			delete(parent.children, element)
   183  		}
   184  
   185  	}
   186  	return true
   187  }