#  PyDia C++ Import
#  Copyright (c) 2006 Hans Breuer <hans@breuer.org>

# Another attempt to import C++ into a diagram. This time it is not trying to parse C++ directly
# but the XML generated by GCC_XML ( http://www.gccxml.org/ ). 

#    This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   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.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

import string, sys

class Node :
	def __init__ (self, name) :
		self.name = name
		self.context = None
	def IsMethod (self) :
		return 0
	def IsUnion (self) :
		return 0
	def IsClass (self) :
		return 0
	def Name (self) :
		return self.name

class Type(Node) :
	def __init__ (self, name) :
		Node.__init__(self, name)
		self.names = []
	def AddName (self, name) :
		# for enumerations
		self.names.append(name)

class Union(Node) :
	def __init__ (self, name) :
		Node.__init__(self, name)
		self.names = []
	def IsUnion (self) :
		return 1
	def AddMember (self, name) :
		self.names.append(name)
	def Name (self) :
		ms = []
		# shortcut to avoid endless recursion with self referencing uions
		return self.name
		for s in self.names :
			ms.append (g_nodes[s].Name())
		return string.join(ms, "; ")

class Fassade(Node) :
	def __init__ (self, type, pre, post) :
		self.type = type
		self.pre = pre
		self.post = post
	def Name (self) :
		if self.pre != "" :
			return self.pre + " " + g_nodes[self.type].Name() + self.post
		else :
			return g_nodes[self.type].Name() + self.post

class Argument(Node) :
	def __init__ (self, name, type) :
		Node.__init__(self, name)
		self.type = type
		self.access = "public"
		self.static = 0
	def Type (self) :
		if g_nodes.has_key (self.type) :
			return g_nodes[self.type].Name()
		return "?"
	def Visibility (self) :
		"This function is mapping from string to dia enum; should probably not be done here"
		if "protected" == self.access : return 2 #UML_PROTECTED
		elif "private" == self.access : return 1 #UML_PRIVATE
		return 0 #UML_PUBLIC

class Function(Node) :
	def __init__ (self, name, type) :
		Node.__init__(self, name)
		self.returns = type
		self.params = []
	def AddArg (self, arg) :
		self.params.append (arg)
	def Type (self) :
		if g_nodes.has_key (self.returns) :
			return g_nodes[self.returns].Name()
		return ""
	def Signature (self) :
		args = []
		ret = ""
		for p in self.params :
			try :
				args.append (p.name + ":" + g_nodes[p.type].Name())
			except AttributeError :
				args.append (":" + p.Name())
			except :
				print "E:", p, p.name, p.type
		if self.returns : 
			ret = g_nodes[self.returns].Name() + " "
		return ret + self.name + " (" + string.join(args, ", ") + ")"

class Method(Function) :
	def __init__ (self, name, type) :
		Function.__init__(self, name, type)
		self.const = 0
		self.static = 0
		self.access = "public"
		self.virtual = 0
	def IsMethod (self) :
		return 1
	def Visibility (self) :
		"This function is mapping from string to dia enum; should probably not be done here"
		if "protected" == self.access : return 2 #UML_PROTECTED
		elif "private" == self.access : return 1 #UML_PRIVATE
		return 0 #UML_PUBLIC
	def InheritanceType (self) :
		if self.virtual > 1 : return 0 #UML_ABSTRACT
		if self.virtual > 0 : return 1 #UML_POLYMORPHIC
		return 2

class Klass(Node) :
	def __init__ (self, name) :
		Node.__init__(self, name)
		self.parents = []
		self.members = []
		self.abstract = 0
	def AddParent (self, id) :
		self.parents.append (id)
	def AddMember (self, id) :
		self.members.append (id)
	def IsClass (self) :
		return 1
	def Name (self) :
		if g_nodes.has_key (self.context) and g_nodes[self.context].Name() != "" :
			return g_nodes[self.context].Name() + "::" + self.name
		else :
			return self.name
	def Parents (self) :
		# full qualified names
		names = []
		for p in self.parents :
			if g_nodes.has_key(p) :
				names.append (g_nodes[p].Name())
		return names
	def Dump (self) :
		ps = ""
		for id in self.parents :
			if g_nodes.has_key (id) :
				ps = ps + " " + g_nodes[id].Name()
		print self.Name() + "(" + ps + " )"
		for id in self.members :
			print "\t", g_nodes[id], id
			if g_nodes[id].IsMethod() :
				print "\t" + g_nodes[id].Signature()
			elif g_nodes[id].IsUnion() :
				print "\t" + g_nodes[id].Name() 
			else :
				try :
					print "\t" + g_nodes[id].Name() + ":" + g_nodes[g_nodes[id].type].Name()
				except AttributeError :
					print "AttributeError:", g_nodes[id]

class Namespace(Node) :
	def __init__ (self, name) :
		Node.__init__(self, name)
		self.name = name
	def Name (self) :
		id = self.context
		if  g_nodes.has_key (id) and g_nodes[id].Name() != "" :
			return g_nodes[id].Name() + "::" + self.name
		else :
			return self.name
	
g_nodes = {}
g_classes = []

def Parse (sFile, nodes) :

	import xml.parsers.expat
	global g_classes

	ctx = []
	def start_element(name, attrs) :
		o = None
		if name in ["Class", "Struct"] :
			#print attrs["name"], attrs["id"]
			o = Klass(attrs["name"])
			if attrs.has_key("bases") :
				bs = string.split (attrs["bases"], " ")
				for s in bs :
					o.AddParent (s)
			if attrs.has_key("members") :
				ms = string.split (attrs["members"], " ")
				for s in ms :
					if s != "" :
						o.AddMember (s)
			if attrs.has_key("abstract") :
				o.abstract = string.atoi(attrs["abstract"])
			g_classes.append (o)
		elif "Union" == name :
			o = Union(attrs["name"])
			if attrs.has_key("members") :
				ms = string.split (attrs["members"], " ")
				for s in ms :
					if s != "" :
						o.AddMember (s)
			# FIXME: this creates a dup
		elif "Namespace" == name :
			#print attrs["name"], attrs["id"], "::"
			if attrs["name"] == "::" :
				o = Namespace("")
			else :
				o = Namespace(attrs["name"])
		elif name in ["Method", "OperatorMethod", "Constructor", "Destructor"] :
			if "Constructor" == name : o = Method (attrs["name"], None)
			elif "Destructor" == name : o = Method ("~" + attrs["name"], None)
			else : o = Method (attrs["name"], attrs["returns"])
			
			if attrs.has_key("virtual") : o.virtual += string.atoi(attrs["virtual"])
			if attrs.has_key("pure_virtual") : o.virtual += string.atoi(attrs["pure_virtual"])
			if attrs.has_key("access") : o.access = attrs["access"]
		elif name in ["Field", "Typedef"] :
			o = Argument (attrs["name"], attrs["type"])
			if attrs.has_key("access") : o.access = attrs["access"]
			if attrs.has_key("static") : o.static = attrs["static"]
		elif name in ["FundamentalType", "Enumeration"] :
			o = Type (attrs["name"])
		elif "ReferenceType" == name :
			o = Fassade (attrs["type"], "", "&")
		elif "PointerType" == name :
			o = Fassade (attrs["type"], "", "*")
		elif "ArrayType" == name :
			o = Fassade (attrs["type"], "", "[]")
		elif "CvQualifiedType" == name :
			o = Fassade (attrs["type"], "const", "")
		elif "Argument" == name :
			if attrs.has_key("name") :
				o = Argument (attrs["name"], attrs["type"])
			else :
				o = Fassade (attrs["type"], "", "")
			if ctx[-1][1] :
				ctx[-1][1].AddArg (o)
			o = None # lookup not possible
		elif "Ellipsis" == name :
			o = Argument ("", "...")
			if ctx[-1][1] :
				ctx[-1][1].AddArg (o)
			o = None # lookup not possible			
		elif "EnumValue" == name :
			if ctx[-1][1] :
				ctx[-1][1].AddName (attrs["name"])
		elif name in ["Function", "OperatorFunction", "FunctionType"] :
			if attrs.has_key("name") :
				o = Function (attrs["returns"], attrs["name"])
			else : # function & type
				o = Function (attrs["returns"], attrs["id"])				
		elif "Variable" == name :
			pass # not ofinterest?
		elif "File" == name :
			pass # FIXME: thrown away
		else :
			print "Unhandled:", name
		if o :
			if attrs.has_key("context") :
				#print attrs["context"]
				o.context = attrs["context"]
			nodes[attrs["id"]] = o
		ctx.append((name, o)) #push
		
	def end_element(name) :
		del ctx[-1] # pop
	def char_data(data) :
		pass

	p = xml.parsers.expat.ParserCreate()
	p.StartElementHandler = start_element
	p.EndElementHandler = end_element
	p.CharacterDataHandler = char_data

	p.Parse(sFile)

def Process (sName) :
	global g_nodes
	f = open (sName)
	Parse(f.read(), g_nodes)

def ImportCpp (sFile, diagramData) :
	# process the c++ file with GCC_XML to get XML
	# TODO
	ImportXml (sXmlFile, diagramData)

def ImportXml (sFile, diagramData) :
	# XML to internal representation
	global g_nodes, g_classes
	g_nodes = {} # these are not interchangeable between diagrams
	g_classes = [] # neither are these
	Process (sFile)
	# internal representation to diagram
	layer = diagramData.active_layer
	# we need some kind of filter to not always generate the whole stl diagram 
	theLinks = {}
	nTotal = 0
	for c in g_classes :
		theLinks[c.Name()] = 0
	for c in g_classes :
		bUsed = 0
		for p in c.Parents() :
			if c.Name()[:5] == "std::" : continue # is this too drastic ?
			if theLinks.has_key(p) :
				theLinks[p] += 1
				bUsed = 1
		if bUsed :
			# to have bottom most visible as well
			theLinks[c.Name()] += 1
			nTotal += 1
	if nTotal < 2 : # arbitrary limit to generate simple diagrams not using inheritance at all
		for c in g_classes :
			if theLinks.has_key(c.Name) :
				theLinks[c.Name()] += 1
	# now everything interesting should be in theLinks with a 'ref count' above zero
	for c in g_classes :
		if not theLinks.has_key(c.Name()) : continue
		if theLinks[c.Name()] :
			theLinks[c.Name()] = c
		else :
			del theLinks[c.Name()]
	theObjects = {}
	for s in theLinks.keys() :
		o, h1, h2 = dia.get_object_type("UML - Class").create(0,0)
		layer.add_object(o)
		o.properties["name"] = s.encode("UTF-8")
		if c.abstract :
			o.properties["abstract"] = 1
		methods = []
		attributes = []
		c = theLinks[s]
		for mid in c.members :
			if not g_nodes.has_key(mid) :
				continue #HACK
			m = g_nodes[mid]
			#print m
			if m.IsMethod () : # (name, type, comment, stereotype, visibility, inheritance_type, query,class_scope, params)
				params = []
				for a in m.params :
					# (name, type, value, comment, kind)
					try  :
						print a.name, a.Type()
						params.append ((a.name.encode("UTF-8"), a.Type().encode("UTF-8"), None, "", 0))
					except :
						pass
				methods.append ((m.name.encode("UTF-8"), m.Type().encode("UTF-8"), "", "", m.Visibility(),m.InheritanceType(),0,0, params))
			elif m.IsUnion () :
				pass
			else : # (name,type,value,comment,visibility,abstract,class_scope)
				try  :
					attributes.append ((m.Name().encode("UTF-8"), m.Type().encode("UTF-8"), "", "", m.Visibility(),0,m.static))
				except  :
					print "Error", m.name
		# set some properties 
		o.properties["operations"] = methods
		o.properties["attributes"] = attributes
		
		theObjects[s] = o
	# class connections
	for s in theLinks.keys() :
		o1 = theObjects[s]
		c = theLinks[s]
		for p in c.Parents() :
			o, h1, h2 = dia.get_object_type("UML - Generalization").create(0,0)
			layer.add_object(o)
			o2 = theObjects[p]
			h1.connect (o2.connections[6])
			h2.connect (o1.connections[1])
	# update placement depending on number of parents ?
	
	layer.update_extents()
	#dia.active_display().add_update_all()
			
if __name__ == '__main__': 
	Process(sys.argv[1])
	for c in g_classes :
		if c.Name()[:2] != "__" and c.Name()[:5] != "std::" :
			try :
				if c.IsClass() :
					c.Dump()
			except KeyError :
				pass
else :
	try :
		import dia
		#dia.register_import("Cpp via GCC_XML", "cpp", ImportCpp)
		dia.register_import("XML from GCC_XML", "xml", ImportXml)
	except ImportError :
		pass
