# PyDia Code Generation from UML Diagram
# Copyright (c) 2005  Hans Breuer <hans@breuer.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 sys, dia

class Klass :
	def __init__ (self, name) :
		self.name = name
		self.attributes = {}
		self.operations = {}
		self.comment = ""
	def AddAttribute(self, name, type, visibility) :
		self.attributes[name] = (type, visibility)
	def AddOperation(self, name, type, visibility, params) :
		self.operations[name] = (type, visibility, params)
	def SetComment(self, s) :
		self.comment = s

class ObjRenderer :
	"Implements the Object Renderer Interface and transforms diagram into its internal representation"
	def __init__ (self) :
		# an empty dictionary of classes
		self.klasses = {}
		self.filename = ""
	def begin_render (self, data, filename) :
		self.filename = filename
		for layer in data.layers :
			# for the moment ignore layer info. But we could use this to spread accross different files
			for o in layer.objects :
				if o.type.name == "UML - Class" :
					#print o.properties["name"].value
					k = Klass (o.properties["name"].value)
					k.SetComment(o.properties["comment"].value)
					for op in o.properties["operations"].value :
						# op : a tuple with fixed placing, see: objects/UML/umloperations.c:umloperation_props
						# (name, type, comment, stereotype, visibility, inheritance_type, class_scope, params)
						params = []
						for par in op[8] :
							# par : again fixed placement, see objects/UML/umlparameter.c:umlparameter_props
							params.append((par[0], par[1]))
						k.AddOperation (op[0], op[1], op[4], params)
					#print o.properties["attributes"].value
					for attr in o.properties["attributes"].value :
						# see objects/UML/umlattributes.c:umlattribute_props
						#print "\t", attr[0], attr[1], attr[4]
						k.AddAttribute(attr[0], attr[1], attr[4])
					self.klasses[o.properties["name"].value] = k
				elif o.type.name == "UML - Generalization" :
					# could setup inheritance here
					pass
				elif o.type.name == "UML - Association" :
					# should already have got attributes relation by names
					pass
				# other UML objects which may be interesting
				# UML - Note, UML - LargePackage, UML - SmallPackage, UML - Dependency, ...
	def end_render(self) :
		# without this we would accumulate info from every pass
		self.attributes = {}
		self.operations = {}

class PyRenderer(ObjRenderer) : 
	def __init__(self) :
		ObjRenderer.__init__(self)
	def end_render(self) :
		f = open(self.filename, "w")
		for sk in self.klasses.keys() :
			f.write ("class %s :\n" % (sk,))
			f.write ("\tdef __init__(self) :\n")
			for sa in self.klasses[sk].attributes.keys() :
				attr = self.klasses[sk].attributes[sa]
				f.write("\t\tself.%s = None # %s\n" % (sa, attr[0]))
			else :
				f.write("\t\tpass\n")
			for so in self.klasses[sk].operations.keys() :
				op = self.klasses[sk].operations[so]
				# we only need the parameter names
				pars = "self"
				for p in op[2] :
					pars = pars + ", " + p[0]
				f.write("\tdef %s (%s) :\n" % (so, pars))
				f.write("\t\t# returns %s\n" % (op[0], ))
				f.write("\t\tpass\n")
		f.close()
		ObjRenderer.end_render(self)

class CxxRenderer(ObjRenderer) :
	def __init__(self) :
		ObjRenderer.__init__(self)
	def end_render(self) :
		f = open(self.filename, "w")
		f.write("/* generated by dia/codegen.py */\n")
		# declaration
		for sk in self.klasses.keys() :
			f.write ("class %s \n{\n" % (sk,))
			k = self.klasses[sk]
			# first sort by visibility
			ops = [[], [], [], []]
			for so in k.operations.keys() :
				t, v, p = k.operations[so]
				ops[v].append((t,so,p))
			vars = [[], [], [], []]
			for sa in k.attributes.keys() :
				t, v = k.attributes[sa]
				vars[v].append((t, sa))
			visibilities = ("public:", "private:", "protected:", "/* implementation: */")
			for v in [0,2,1,3] :
				if len(ops[v]) == 0 and len(vars[v]) == 0 :
					continue
				f.write ("%s\n" % visibilities[v])
				for op in ops[v] :
					# detect ctor/dtor
					so = ""
					if sk == op[1] or ("~" + sk) == op[1] :
						so = "\t%s (" % (op[1])
					else :
						so = "\t%s %s (" % (op[0], op[1])
					f.write (so)
					# align parameters with the opening brace
					n = len(so)
					i = 0
					m = len(op[2]) - 1
					for p in op[2] :
						linefeed = ",\n\t" + " " * (n - 1) 
						if i == m :
							linefeed = ""
						f.write ("%s %s%s" % (p[1], p[0], linefeed))
						i = i + 1
					f.write(");\n")
				for var in vars[v] :
					f.write("\t%s %s;\n" % (var[0], var[1]))
			f.write ("};\n\n")
		# implementation
		# ...
		f.close()
		ObjRenderer.end_render(self)

# dia-python keeps a reference to the renderer class and uses it on demand
dia.register_export ("PyDia Code Generation (Python)", "py", PyRenderer())
dia.register_export ("PyDia Code Generation (C++)", "cxx", CxxRenderer())
