#!/usr/bin/env python
#############################################################################
# Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1997, 1998, 1999
# All Rights Reserved.
#
# The software contained on this media is the property of the DSTC Pty
# Ltd.  Use of this software is strictly in accordance with the
# license agreement in the accompanying LICENSE.HTML file.  If your
# distribution of this software does not contain a LICENSE.HTML file
# then you have no rights to use this software in any manner and
# should contact DSTC at the address below to determine an appropriate
# licensing arrangement.
# 
#      DSTC Pty Ltd
#      Level 7, GP South
#      Staff House Road
#      University of Queensland
#      St Lucia, 4072
#      Australia
#      Tel: +61 7 3365 4310
#      Fax: +61 7 3365 4311
#      Email: enquiries@dstc.edu.au
# 
# This software is being provided "AS IS" without warranty of any
# kind.  In no event shall DSTC Pty Ltd be liable for damage of any
# kind arising out of or in connection with the use or performance of
# this software.
#
# Project:      Fnorb
# File:         $Source: /projects/fnorb/Fnorb-1.1/compiler/RCS/StubGenerator.py,v $
# Version:      @(#)$RCSfile: StubGenerator.py,v $ $Revision: 1.9 $
#
#############################################################################
""" Python stub generator for CORBA IDL. """


# Standard/built-in modules.
import string

# Fnorb modules.
from Fnorb.orb import CORBA, Util

# Compiler modules.
import CodeGenerator


class StubGenerator(CodeGenerator.CodeGenerator):
    """ Python stub generator for CORBA IDL. """

    #########################################################################
    # CodeGenerator protected interface.
    #########################################################################

    def _generate_attribute(self, context, file, ifr_object, indent):
	""" Generate Python code for an IDL attribute. """

	# Get the description of the attribute.
	description = ifr_object.describe()
	attribute_desc = description.value.value()

        #####################################################################
	# Accessor.
        #####################################################################

	# Method header.
	file.write(self._indent(indent))
	file.write('def _get_%s(self):\n' % attribute_desc.name)

	# Indent.
	indent = indent + 1

	# Documentation string.
	file.write(self._indent(indent))
	file.write('""" Attribute: %s """\n\n' % attribute_desc.id)

	# Create the output typecode.
	file.write(self._indent(indent))
	file.write('# Typecode for the attribute value.\n')
	file.write(self._indent(indent))
	file.write('outputs = []\n')
	file.write(self._indent(indent))
	file.write('outputs.append(')
	file.write(self._get_typecode(attribute_desc.type))
	file.write(')\n\n')

	# Boiler plate.
	file.write(self._indent(indent))
	file.write('# Create a request object.\n')
	file.write(self._indent(indent))
	file.write('request = self._create_request(')
	file.write('"_get_%s", ' % attribute_desc.name)
	file.write('[], outputs, [])\n\n')
	file.write(self._indent(indent))
	file.write('# Make the request!\n')
	file.write(self._indent(indent))
	file.write('request.invoke()\n\n')

	file.write(self._indent(indent))
	file.write('# Return the attribute value.\n')
	file.write(self._indent(indent))
	file.write('return request.results()\n\n')

	# Outdent.
	indent = indent - 1

        #####################################################################
	# Modifier (unless the attribute is read only ;^).
        #####################################################################

	if attribute_desc.mode != CORBA.ATTR_READONLY:
	    # Method header.
	    file.write(self._indent(indent))
	    file.write('def _set_%s(self, _value):\n' % attribute_desc.name)

	    # Indent.
	    indent = indent + 1

	    # Documentation string.
	    file.write(self._indent(indent))
	    file.write('""" Attribute: %s """\n\n' % attribute_desc.id)

	    # Create the input typecodes.
	    file.write(self._indent(indent))
	    file.write("# Typecode for the attribute value.\n")
	    file.write(self._indent(indent))
	    file.write('inputs = []\n')

	    file.write(self._indent(indent))
	    file.write('inputs.append(')
	    file.write(self._get_typecode(attribute_desc.type))
	    file.write(')\n\n')

	    # Boiler plate.
	    file.write(self._indent(indent))
	    file.write('# Create a request object.\n')
	    file.write(self._indent(indent))
	    file.write('request = self._create_request(')
	    file.write('"_set_%s", ' % attribute_desc.name)
	    file.write('inputs, [], [])\n\n')
	    file.write(self._indent(indent))
	    file.write('# Make the request!\n')
	    file.write(self._indent(indent))
	    file.write('request.invoke(_value)\n\n')

	    file.write(self._indent(indent))
	    file.write('return\n\n')

	return

    def _generate_constant(self, context, file, ifr_object, indent):
	""" Generate Python code to represent an IDL constant. """

	# Get the description of the constant.
	description = ifr_object.describe()
	const_desc = description.value.value()

	# Make sure that the constant name is not a Python keyword.
	py_const_name = Util.python_name(const_desc.name)

	# Repository id comment.
	file.write(self._indent(indent))
	file.write('# Constant: %s\n' % const_desc.id)

	# Create and initialise a corresponding Python variable.
	file.write(self._indent(indent))
	file.write('%s = %s' % (py_const_name, repr(const_desc.value.value())))
	file.write('\n\n')

	# Get the constant's typecode.
	typecode = const_desc.type

	# Add a typecode constant to the stub.
	self._write_typecode_constant(file, indent, const_desc.id, typecode,
				      py_const_name)

	return

    def _generate_exception(self, context, file, ifr_object, indent):
	""" Generate Python code to represent an IDL exception. """

	# Get the description of the exception.
	description = ifr_object.describe()
	exception_desc = description.value.value()

	# Make sure that the structure name is not a Python keyword.
	py_exception_name = Util.python_name(exception_desc.name)

	# Get the members of the exception.
	members = ifr_object._get_members()

	# Class header (inheriting from CORBA.UserException).
	file.write(self._indent(indent))
	file.write('class %s(Fnorb.orb.CORBA.UserException):\n' \
		   % py_exception_name)
	# Indent.
	indent = indent + 1

	# Documentation string.
	file.write(self._indent(indent))
	file.write('""" Exception: %s """\n\n' % exception_desc.id)

	# Repository id attribute.
	file.write(self._indent(indent))
	file.write('_FNORB_ID = "%s"\n\n' % exception_desc.id)

	# Create a constructor parameter for every member of the exception.
	parameters = ['self']
	for member in members:
	    parameters.append('_' + member.name)

	# Method header for the constructor.
	file.write(self._indent(indent))
	file.write('def __init__(%s):\n' % string.join(parameters, ', '))

	# Indent.
	indent = indent + 1

	# Documentation string.
	file.write(self._indent(indent))
	file.write('""" Constructor. """\n\n')

	# Initialise every member of the exception in the constructor body.
	for member in members:
	    # Make sure that the member name is not a Python keyword.
	    py_member_name = Util.python_name(member.name)
	    file.write(self._indent(indent))
	    file.write("self.%s = _%s\n" % (py_member_name, member.name))

	# Return statement.
	file.write(self._indent(indent))
	file.write('return\n\n')
	    
	# Outdent.
	indent = indent - 1

	# Create the __getinitargs__ method.
	if len(members) > 0:
	    # Method header.
	    file.write(self._indent(indent))
	    file.write('def __getinitargs__(self):\n')

	    # Indent.
	    indent = indent + 1

	    # Documentation string.
	    file.write(self._indent(indent))
	    file.write('""" ')
	    file.write('Return the constructor arguments for unpickling.')
	    file.write(' """\n\n')

	    # Special case for the singleton tuple ;^)
	    if len(members) == 1:
		# Make sure that the member name is not a Python keyword.
		py_member_name = Util.python_name(members[0].name)
		file.write(self._indent(indent))
		file.write('return (self.%s,)\n\n' % py_member_name) 

	    else:
		results = []
		for member in members:
		    # Make sure that the member name is not a Python keyword.
		    py_member_name = Util.python_name(member.name)
		    results.append('self.%s' % py_member_name)

		file.write(self._indent(indent))
		file.write('return (%s)\n\n' % string.join(results, ', '))

	    # Outdent.
	    indent = indent - 1
		
	# Outdent.
	indent = indent - 1

	# Get the exceptions's typecode.
	typecode = ifr_object._get_type()

	# Add a typecode constant to the stub.
	self._write_typecode_constant(file, indent, exception_desc.id,
				      typecode, py_exception_name)

	return

    def _generate_interface(self, context, file, ifr_object, indent):
	""" Generate Python code to represent an IDL interface. """

	# Get the description of the interface.
	description = ifr_object.describe()
	interface_desc = description.value.value()

	# Make sure that the interface name is not a Python keyword.
	py_interface_name = Util.python_name(interface_desc.name)

	# Get the scoped name of the interface.
	interface_scoped_name =Util.ScopedName(ifr_object._get_absolute_name())

	# Fix up any clashes with Python keywords.
	interface_scoped_name.pythonise()

	# Base interfaces.
	bases = ['Fnorb.orb.CORBA.Object']
	packages = []

	for base in ifr_object._get_base_interfaces():
	    # Get the scoped name of the base interface.
	    base_scoped_name = Util.ScopedName(base._get_absolute_name())

	    # Fix up any clashes with Python keywords.
	    base_scoped_name.pythonise()

	    # If the base interface is defined in a different IDL module (ie.
	    # in a different Python package).
	    if base_scoped_name[:-1] != interface_scoped_name[:-1]:
		# If the base interface is defined at the global scope then
		# add the name of the global package.
		if len(base_scoped_name) == 1:
		    base_scoped_name.insert(0, context.globals())

		# Use the full scoped name in the Python class header.
		base_python_name = base_scoped_name.join('.')

		# Add the Python package that the base interface is defined in
		# to the list of packages to import.
		packages.append(base_scoped_name[:-1].join('.'))

	    # Otherwise, the base interface is defined in the same IDL module
	    # as the interface itself, so we just use the base interface name
	    # in the Python class header.
	    else:
		base_python_name = base_scoped_name[-1]

	    # Add to the list of base classes for the class header.
	    bases.append(base_python_name)
		
	# Import base interface packages.
	if len(packages) > 0:
	    file.write(self._indent(indent))
	    file.write('# Import base interface packages.\n')
	    for package in packages:
		file.write(self._indent(indent))
		file.write(context.create_import_statement(package))
		file.write('\n')

	    file.write('\n')

	# Class header.
	file.write(self._indent(indent))
	file.write('class %s' % py_interface_name)
	file.write('(%s):\n' % string.join(bases, ', '))

	# Indent.
	indent = indent + 1
	
	# Documentation string.
	file.write(self._indent(indent))
	file.write('""" Interface: %s """\n\n' % interface_desc.id)

	# Repository id attribute.
	file.write(self._indent(indent))
	file.write('_FNORB_ID = "%s"\n\n' % interface_desc.id)

	# Generate code for every definition contained in the interface
	# (ignoring inherited definitions).
	contents = ifr_object.contents(CORBA.dk_all, 1)
	if len(contents) > 0:
	    for contained in contents:
		self.generate(context, file, contained, indent)

	# Empty interface.
	else:
	    file.write(self._indent(indent))
	    file.write('pass\n\n')

	# Outdent.
	indent = indent - 1

	# Get the interface's typecode.
	typecode = ifr_object._get_type()

	# Add a typecode constant to the stub.
	self._write_typecode_constant(file, indent, interface_desc.id,
				      typecode, py_interface_name)

	return

    def _generate_module(self, context, file, ifr_object, indent):
	""" Generate Python code to represent an IDL module. """

	# Get the description of the module.
	description = ifr_object.describe()
	module_desc = description.value.value()

	# Get the scoped name of the module.
	package = Util.ScopedName(ifr_object._get_absolute_name())

	# Fix up any clashes with Python keywords.
	package.pythonise()

	# Create a Python package to represent the IDL module.
	file = self._create_package(context, package.join('/'), module_desc.id)

	# Generate code for every definition contained in the module.
	for contained in ifr_object.contents(CORBA.dk_all, 0):
	    self.generate(context, file, contained, 0)

	# End of the package.
	self._end_package(file, indent)

	# All done!
	file.close()

	return

    def _generate_operation(self, context, file, ifr_object, indent):
	""" Generate Python code to represent an IDL operation. """

	# Get the description of the operation.
	description = ifr_object.describe()
	operation_desc = description.value.value()

	# Make sure that the operation name is not a Python keyword.
	py_operation_name = Util.python_name(operation_desc.name)

	# Method header.
	file.write(self._indent(indent))
	file.write('def %s(self, *args, **kw):\n' % py_operation_name)

	# Indent.
	indent = indent + 1

	# Documentation string.
	file.write(self._indent(indent))
	file.write('""" Operation: %s """\n\n' % operation_desc.id)

	# Create the input typecodes.
	file.write(self._indent(indent))
	file.write("# Typecodes for 'in' and 'inout' parameters.\n")
	file.write(self._indent(indent))
	file.write('inputs = []\n')

	for p in operation_desc.parameters:
	    if p.mode == CORBA.PARAM_IN or p.mode == CORBA.PARAM_INOUT:
		file.write(self._indent(indent))
		file.write('inputs.append(%s)' % self._get_typecode(p.type))
		file.write('\n')

	file.write('\n')
		
	# Create the output typecodes.
	file.write(self._indent(indent))
	file.write("# Typecodes for the result, 'inout' and 'out' parameters.")
	file.write('\n')
	file.write(self._indent(indent))
	file.write('outputs = []\n')

	# The result.
	if operation_desc.result.kind() != CORBA.tk_void:
	    file.write(self._indent(indent))
	    file.write('outputs.append(')
	    file.write(self._get_typecode(operation_desc.result))
	    file.write(')\n')

	# 'inout' and 'out' parameters.
	for p in operation_desc.parameters:
	    if p.mode == CORBA.PARAM_INOUT or p.mode == CORBA.PARAM_OUT:
		file.write(self._indent(indent))
		file.write('outputs.append(%s)' % self._get_typecode(p.type))
		file.write('\n')

	file.write('\n')

	# Create the exception typecodes.
	file.write(self._indent(indent))
	file.write("# Typecodes for user exceptions.\n")
	file.write(self._indent(indent))
	file.write('exceptions = []\n')

	for ex in operation_desc.exceptions:
	    file.write(self._indent(indent))
	    file.write('exceptions.append(Fnorb.orb.CORBA.typecode("%s"))\n' \
		       % ex.id)

	file.write('\n')
	
	# Boiler plate.
	file.write(self._indent(indent))
	file.write('# Create a request object.\n')
	file.write(self._indent(indent))
	file.write('request = self._create_request("')
	file.write(operation_desc.name)
	file.write('", inputs, outputs, exceptions)\n\n')
	file.write(self._indent(indent))
	file.write('# Make the request!\n')

	if operation_desc.mode == CORBA.OP_ONEWAY:
	    file.write(self._indent(indent))
	    file.write('apply(request.invoke_oneway, args, kw)\n\n')
	    file.write(self._indent(indent))
	    file.write('return\n\n')
	
	else:
	    file.write(self._indent(indent))
	    file.write('apply(request.invoke, args, kw)\n\n')
	    file.write(self._indent(indent))
	    file.write('# Return the results.\n')
	    file.write(self._indent(indent))
	    file.write('return request.results()\n\n')

	return
			
    def _generate_struct(self, context, file, ifr_object, indent):
	""" Generate Python code to represent an IDL structure. """

	# Get the description of the structure.
	description = ifr_object.describe()
	struct_desc = description.value.value()

	# Make sure that the structure name is not a Python keyword.
	py_struct_name = Util.python_name(struct_desc.name)

	# Get the members of the structure.
	members = ifr_object._get_members()

	# Class header.
	file.write(self._indent(indent))
	file.write('class %s:\n' % py_struct_name)

	# Indent.
	indent = indent + 1

	# Documentation string.
	file.write(self._indent(indent))
	file.write('""" Struct: %s """\n\n' % struct_desc.id)

	# Repository id attribute.
	file.write(self._indent(indent))
	file.write('_FNORB_ID = "%s"\n\n' % struct_desc.id)

	# Create a constructor parameter for every member of the structure.
	parameters = ['self']
	for member in members:
	    parameters.append('_' + member.name)

	# Method header for the constructor.
	file.write(self._indent(indent))
	file.write('def __init__(%s):\n' % string.join(parameters, ', '))

	# Indent.
	indent = indent + 1

	# Documentation string.
	file.write(self._indent(indent))
	file.write('""" Constructor. """\n\n')

	# Initialise every member of the structure in the constructor body.
	for member in members:
	    # Make sure that the member name is not a Python keyword.
	    py_member_name = Util.python_name(member.name)
	    file.write(self._indent(indent))
	    file.write("self.%s = _%s\n" % (py_member_name, member.name))

	# Return statement.
	file.write(self._indent(indent))
	file.write('return\n\n')
	    
	# Outdent.
	indent = indent - 1

	# Create the __getinitargs__ method.
	if len(members) > 0:
	    # Method header.
	    file.write(self._indent(indent))
	    file.write('def __getinitargs__(self):\n')

	    # Indent.
	    indent = indent + 1

	    # Documentation string.
	    file.write(self._indent(indent))
	    file.write('""" ')
	    file.write('Return the constructor arguments for unpickling.')
	    file.write(' """\n\n')

	    # Special case for the singleton tuple ;^)
	    if len(members) == 1:
		# Make sure that the member name is not a Python keyword.
		py_member_name = Util.python_name(members[0].name)
		file.write(self._indent(indent))
		file.write('return (self.%s,)\n\n' % py_member_name) 

	    else:
		results = []
		for member in members:
		    # Make sure that the member name is not a Python keyword.
		    py_member_name = Util.python_name(member.name)
		    results.append('self.%s' % py_member_name)

		file.write(self._indent(indent))
		file.write('return (%s)\n\n' % string.join(results, ', '))

	    # Outdent.
	    indent = indent - 1

	# Outdent.
	indent = indent - 1

	# Get the structure's typecode.
	typecode = ifr_object._get_type()

	# Add a typecode constant to the stub.
	self._write_typecode_constant(file, indent, struct_desc.id, typecode,
				      py_struct_name)

	return

    def _generate_union(self, context, file, ifr_object, indent):
	""" Generate Python code to represent an IDL union. """

	# Get the description of the structure.
	description = ifr_object.describe()
	union_desc = description.value.value()

	# Make sure that the union name is not a Python keyword.
	py_union_name = Util.python_name(union_desc.name)

	# Class header.
	file.write(self._indent(indent))
	file.write('class %s(Fnorb.orb.Util.Union):\n' % py_union_name)

	# Indent.
	indent = indent + 1

	# Documentation string.
	file.write(self._indent(indent))
	file.write('""" Union: %s """\n\n' % union_desc.id)

	# Repository id attribute.
	file.write(self._indent(indent))
	file.write('_FNORB_ID = "%s"\n\n' % union_desc.id)

	file.write(self._indent(indent))
	file.write('pass\n\n')

	# Outdent.
	indent = indent - 1

	# Get the unions's typecode.
	typecode = ifr_object._get_type()

	# Add a typecode constant to the stub.
	self._write_typecode_constant(file, indent, union_desc.id, typecode,
				      py_union_name)

	return

    def _generate_enum(self, context, file, ifr_object, indent):
	""" Generate Python code to represent an IDL enumeration. """

	# Get the description of the enumeration.
	description = ifr_object.describe()
	enum_desc = description.value.value()

	# Make sure that the enumeration name is not a Python keyword.
	py_enum_name = Util.python_name(enum_desc.name)

	# Get the members of the enumeration.
	members = ifr_object._get_members()

	# Repository id comment.
	file.write(self._indent(indent))
	file.write('# Enum: %s\n' % enum_desc.id)

	# Create an Util.EnumMember instance for every member of the
	# enumeration.
	i = 0
	for member in members:
	    # Make sure that the member name is not a Python keyword.
	    py_member_name = Util.python_name(member)
	    file.write(self._indent(indent))
	    file.write('%s = ' % py_member_name)
	    file.write('Fnorb.orb.Util.EnumMember("%s", %d)\n' % (member, i))
	    i = i + 1

	# Use the enumeration name to create an instance of the 'Util.Enum'
	# class to emulate a list containing all of the enumeration members.
	file.write(self._indent(indent))
	py_member_names = string.join(map(Util.python_name, members), ', ')
	file.write('%s = Fnorb.orb.Util.Enum("%s", [%s])\n\n' \
		   % (py_enum_name, enum_desc.id, py_member_names))

	# Get the enum's typecode.
	typecode = ifr_object._get_type()

	# Add a typecode constant to the stub.
	self._write_typecode_constant(file, indent, enum_desc.id, typecode,
				      py_enum_name)
		   
	return

    def _generate_alias(self, context, file, ifr_object, indent):
	""" Generate Python code to represent an IDL alias. """

	# Get the description of the alias.
	description = ifr_object.describe()
	alias_desc = description.value.value()

	# Repository id comment.
	file.write(self._indent(indent))
	file.write('# Alias: %s\n' % alias_desc.id)

	# 'Unwind' the alias until we find a 'real' definition!
	type_def = ifr_object._get_original_type_def()
	while type_def._get_def_kind() == CORBA.dk_Alias:
	    type_def = type_def._get_original_type_def()

	# Get the 'real' typecode.
	typecode = type_def._get_type()

	# Add a typecode constant to the stub.
	self._write_typecode_constant(file, indent, alias_desc.id, typecode,
				      'None')

	return

#############################################################################
