#
# FtpCube
# Copyright (C) 2001 Michael Gilfix
#
# This file is part of FtpCube.
#
# You should have received a file COPYING containing license terms
# along with this program; if not, write to Michael Gilfix
# (mgilfix@eecs.tufts.edu) for a copy.
#
# This version of FtpCube is open source; you can redistribute it and/or
# modify it under the terms listed in the file COPYING.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#

import types
import string # For backwards compatibility with 1.5.x

class CacheNode:

	"""A Cache Node

	A container object for a node in the cache tree."""

	def __init__ (self):

		self.name = None
		self.parent = None
		self.data = None
		self.children = { }

class CacheTree:

	"""A Tree Caching object.

	Provides a cache for storing hierachical data, specifically file
	paths. Paths are given by node names separated by a delimiter.
	The CacheTree allows for such operations as node creation,
	searching, and deletion."""

	def __init__ (self, delim='/'):
		"""Creates the cache by initializing the root. 'delim'
		is the character delimiter that separates the hierachical
		path between node names."""

		self.root = CacheNode ()
		self.root.name = delim
		self.delim = delim

	def get_delim (self):
		"""Returns the delimeter."""

		return self.delim

	def __path_to_list (self, path):
		"""Takes the hierarchical 'path' and splits it using the
		delimiter. Blank entries from the split are then filtered.
		"""

		if type (path) == types.StringType:
			names = filter (lambda x: x, string.split (path, self.delim))
		elif type (path) == types.ListType:
			names = path
		else:
			raise TypeError, "called find_node with invalid path type"
		return names

	def find_node (self, path):
		"""Finds the node associated with the 'path'. Returns the node
		if found, and None otherwise."""

		names = self.__path_to_list (path)
		node = self.root

		while names and node is not None:
			node_name = names.pop (0)
			if node.children.has_key (node_name):
				node = node.children[node_name]
			else:
				node = None
		return node

	def get_node_list (self, path):
		"""Returns a list of nodes along the 'path'. If the path is
		invalid, None is returned."""

		names = self.__path_to_list (path)
		node = self.root
		node_list = [ node ]

		while names and node is not None:
			node_name = names.pop (0)
			if node.children.has_key (node_name):
				node = node.children[node_name]
				node_list.append (node)
			else:
				node = None
		return node_list

	def get_root (self):
		"""Returns the root node."""

		return self.root

	def get_children (self, node):
		"""Returns the list of children associated with the root node.
		No order is preserved here."""

		return node.children.keys ()

	def get_child_node (self, node, child):
		"""Returns the object node 'child' who is a valid child of 'node'.
		Otherwise, None is returned."""

		if node.children.has_key (child):
			return node.children[child]
		return None

	def remove_child (self, node, child):
		"""Removes a 'child' from the cache tree with 'node' as its
		parent."""

		if node.children.has_key (child):
			child = node.children[child]
			del node.children[child]
			return child
		return None

	def remove_node (self, path):
		"""Removes the node found at 'path' from the tree."""

		node = self.find_node (path)
		if node.parent is not None:
			del node.parent.children[node.name]
		return node

	def add_node (self, path):
		"""Adds a node whose entry in the hierachical tree corresponds to
		'path'. If sub nodes are missing during the creation, they are
		created and thier children set accordingly."""

		new_node = CacheNode ()
		node = self.root
		names = filter (lambda x: x, string.split (path, self.delim))
		if not names:
			return node
		name = names.pop ()

		while names:
			node_name = names.pop (0)
			if not node.children.has_key (node_name):
				path_node = CacheNode ()
				path_node.name = node_name
				node.children[node_name] = path_node
			node = node.children[node_name]

		new_node.name = name
		new_node.parent = node
		node.children[name] = new_node
		return new_node
