Files
2016-11-01 13:24:49 -04:00

370 lines
13 KiB
Python

## **************
## Copyright 2015 MIT Lincoln Laboratory
## Project: PACE
## Authors: ATLH
## Description: Visiblity nodes
## Modifications:
## Date Name Modification
## ---- ---- ------------
## 17 Feb 2015 ATLH Original file - based on
## ColumnVisibility.java in Accumulo
## code base
## **************
import os
import sys
this_dir = os.path.dirname(os.path.dirname(__file__))
base_dir = os.path.join(this_dir, '../..')
sys.path.append(base_dir)
from enum import IntEnum
import StringIO
class VisibilityFormatException(Exception):
""" Exception raised when unable to process a vis label
Attributes:
msg - error message for situation
"""
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
class NodeType(IntEnum):
"""
Represents the four types of nodes.
EMPTY: exactly what it says on the tin, an empty node
TERM: This is a visibility label term, found at the leaves
of the tree. For example in 'alice&bob', both 'alice' and
'bob' would be the terms.
OR: Node for an or clause, children are either other OR/AND
nodes or terminal TERM nodes.
AND: Node for an and clause, children are either other OR/AND
nodes or terminal TERM nodes.
"""
EMPTY = 0
TERM = 1
OR = 2
AND = 3
VALID_CHAR = ['_','-',':','.','/']
class VisNode(object):
"""
Basic building block to form visibility trees. Stores the
type of the node and the start and the end locations of
the term in the expression, does not actually store the
term itself. Can have multiple children if OR/AND node:
for example a&b&c would have three children.
"""
def __init__(self, start, end=None, type=NodeType.TERM):
'''
Arguments:
start: integer representing the start of the term,
in the case of OR/AND nodes, this is just the
location of the '&'/'|'
end: integer representing the end of the term,
in the case of OR/AND nodes, this is just the
location of the '&'/|'. If not specified, is assumed
to be one more than the start.
type: The type of the node as specified in NodeType.
Defaults to TERM.
'''
self.type = type
self.start = start
self.children = []
if end:
self.end = end
else:
self.end = start + 1
def copy_node(self, node):
'''
Returns a copy of the node of the same type with
the exception of its children.
'''
return VisNode(start=node.start,
end=node.end,
type=node.type)
def add(self, child):
"""
Adds the child to the end of list of children
"""
self.children.append(child)
def getTerm(self, expression):
'''
When passed the original expression for the node
returns the associated term (without quotes if they exist).
Throws an assertion if the node is not a term node.
'''
assert self.type == NodeType.TERM
if expression[self.start] == '"':
return expression[(self.start+1):(self.end-1)]
else:
return expression[self.start:self.end]
class VisTree(object):
"""
Represents a container for a root VisNode and the
corresponding expression
"""
def __init__(self, root, expression):
self.root = root
self.expression = expression
def __str__(self):
"""
Overloading str function, does not simply
return self.expression because it is used
primarily for testing, and it is useful to
see the structure of the tree.
"""
s = StringIO.StringIO()
self._stringify(self.root,self.expression, s)
s_string = s.getvalue()
s.close()
return s_string
def _stringify(self, root, expression, out_put):
"""
Arguments:
root: VisNode to turn into a string representation
exression: the corresponding expression for the
root node passed in
out_put: StringIO object in which the string
description is placed.
Side Effects: Returns nothing, but string representation
is placed in the out_put object.
"""
#case of empty root
if root is None:
out_put.write('')
return
if root.type == NodeType.TERM:
out_put.write(expression[root.start:root.end])
else:
sep = ''
for c in root.children:
out_put.write(sep)
parens = (c.type != NodeType.TERM) and (root.type != c.type)
if parens:
out_put.write("(")
self._stringify(c, expression, out_put)
if parens:
out_put.write(")")
if root.type == NodeType.AND:
sep = '&'
else:
sep = "|"
class VisParser(object):
"""
Contains the logic for parsing visibility labels and turning into
VisTree
"""
def _getTreeType(self, node, expression):
return VisTree(node, expression)
def parse(self, expression):
"""
Arguments:
expression - the expression to parse
Returns: VisTree containing the root node of
the parsed Visibility Tree and the original expression.
Raises VisibilityFormatException if the visibility
is ill-formed.
"""
self._index = 0 #current parse location
self._parens = 0 #number of parenthesis encountered
if len(expression) == 0:
return None
node = self._parse(expression)
if node is None:
raise VisibilityFormatException("operator or missing parens: %s" % (expression))
if self._parens != 0:
raise VisibilityFormatException("parenthesis mis-match: %s" % (expression))
return self._getTreeType(node, expression)
def _isValidAuthChar(self, c):
"""
Determines if the character c is an authorized character for the
visibility field
"""
return any([c >= 'a', c <= 'z',
c >= 'A', c <= 'Z',
c >= '0', c <= '9',
c in VALID_CHAR])
def _processTerm(self, start, end, node_expr, expression):
"""
Arguments:
start - start of the term
end - end of the term
node_expr - Existing term node or None
expression - the overall expression
Returns: Processed term node; if expr existed, it returns
the existing node, otherwise creates a new VisNode for
the term
"""
if start != end:
if node_expr is not None:
raise VisibilityFormatException("expression %s needs to be | or &" % (expression))
return VisNode(start = start, end = end)
if node_expr is None:
raise VisibilityFormatException("empty term %s" % (expression))
return node_expr
def _create_node(self, start, type):
return VisNode(start = start, type = type)
def _parse(self, expression):
"""
Arguments:
expression - the expression to parse
Returns: Root node to the newly parsed visibility tree
"""
result = None #current top-level node
expr = None #child node being parsed
wholeTermStart = self._index #start of the top-level term
subtermStart = self._index #start of sub-level term
subtermComplete = False #has the subterm been completed
#loop through all characters and parse them individually
while self._index < len(expression):
e = expression[self._index]
self._index += 1
if e == '&': #case of whole term being being AND
expr = self._processTerm(subtermStart, self._index - 1, expr, expression)
if result != None:
if result.type != NodeType.AND:
raise VisibilityFormatException("cannot mix & and |")
else:
result = self._create_node(wholeTermStart, NodeType.AND )
result.add(expr)
expr = None
subtermStart = self._index
subtermComplete = False
continue
elif e == '|': #case of whole term being OR
expr = self._processTerm(subtermStart, self._index - 1, expr, expression)
if result != None:
if result.type != NodeType.OR:
raise VisibilityFormatException("cannot mix & and |")
else:
result = self._create_node(wholeTermStart, NodeType.OR )
result.add(expr)
expr = None
subtermStart = self._index
subtermComplete = False
continue
elif e == '(': #case of start of a parenthetical term
self._parens += 1
if (subtermStart != self._index - 1) or (expr is not None):
raise VisibilityFormatException("expression needs & or |")
#parse the subterm expression
expr = self._parse(expression)
subtermStart = self._index
subtermComplete = False
continue
elif e == ')': #case of end of a parenthetical term
self._parens -= 1
#process the subterm and make sure it is wellformed
child = self._processTerm(subtermStart, self._index - 1, expr, expression)
if child is None and result is None:
raise VisibilityFormatException("empty expression not allowed")
if result is None:
return child
#if the child is the same type as parent (result), promote the child's children
if result.type == child.type:
for c in child.children:
result.add(c)
else:
result.add(child)
result.end = self._index - 1
return result
elif e == '"': #case of a quoted expression
if subtermStart != (self._index - 1):
raise VisibilityFormatException("expression needs & or |")
#check to make nothing is invalid escaped in the quotes
while (self._index < len(expression) and \
expression[self._index] != '"'):
if expression[self._index] == '\\':
self._index += 1
if (expression[self._index] != '\\' and \
expression[self._index] != '"'):
raise VisibilityFormatException('invalid escaping within quotes')
self._index += 1
#case without a closing quote
if self._index == len(expression):
raise VisibilityFormatException("unclosed quote")
if subtermStart + 1 == self._index:
raise VisibilityFormatException("empty term")
self._index += 1
subtermComplete = True
continue
else: #all other cases, should be valid characters part of a term
if subtermComplete:
raise VisibilityFormatException("expression needs & or |")
#check to see the character is valid
c = expression[self._index - 1]
if not self._isValidAuthChar(c):
raise VisibilityFormatException('bad character (%c)' % (c))
continue
child = self._processTerm(subtermStart, self._index, expr, expression)
if result is not None:
result.add(child)
result.end = self._index
else:
result = child
if result.type != NodeType.TERM:
if len(result.children) < 2:
raise VisibilityFormatException("missing term")
return result