/*
 *  ServerReplyBuilder.java v0.11 11-JAN-2000
 *  Copyright (c) TKK/TLM/Calypso
 *  Author: Alexey Mednonogov
 */

package codec.build;

import java.io.*;
import java.util.*;

import codec.*;
import codec.adapt.*;
import codec.convert.*;
import codec.debug.*;
import codec.dyntree.*;
import codec.export.*;
import codec.orb.*;
import codec.pco.*;
import codec.server.*;
import codec.client.*;
import codec.visit.*;
import codec.build.*;

/** Class for constructing Reply PDU, based on introspection of CORBA
 *  response returned by CORBA object. */
public class ServerReplyBuilder {

	// ------------------------- OUTPUT PARAMETERS -----------------------
   	
    /** PDU constructed by Builder. */
   	protected com.t3.ot.pco.ASP asp;
	public com.t3.ot.pco.ASP getAsp() { return asp; }

    // ------------------------- INTERNAL VARIABLES ----------------------

   	/** Reference to CORBA ORB. */
   	protected org.omg.CORBA.ORB orb;

    // --------------------------- INTERNAL STATE ------------------------

   	/** Depth of introspection level we are currently in. */
	protected int introspectionLevel;
	protected static final int MAX_INTROSPECTION_LEVELS = 512;

	/** Type of IDL structure (choice, sequence, array, struct, none)
	 *  lately visited while constructing PDU. Determines what kind of
     *  of prefix, if any, shall be added in front of field name. */
	protected int[] lastStructType;

    protected static final int STRUCT_TYPE_UNION    = 0;
    protected static final int STRUCT_TYPE_SEQUENCE = 1;
    protected static final int STRUCT_TYPE_ARRAY    = 2;
    protected static final int STRUCT_TYPE_STRUCT   = 3;
    protected static final int STRUCT_TYPE_NONE     = 4;

	/** Did we already parse first element of "DynIterator", where return
     *  value which requires some special handling might have been placed. */
	protected boolean isFirstMemberProcessed;

    /** Is return value present in "DynIterator". It may be absent if it
	 *  is of type "void". */
	protected boolean isRetValuePresent;

	/* Are we processing Reply PDU or body of Exception PDU. */
	protected boolean isExcBody;

	// ------------------------- CLASS CONSTRUCTOR -----------------------

   	public ServerReplyBuilder() {

		CodecObject codec = CorbaServer.getCodecObject();
        orb = codec.getOrbGeneric().getORB();
	}

	// ------------------------- CLASS OPERATIONS ------------------------

	/** Construct PDU based on information from operation response. */
	public void construct(ServantRequest servantRequest) {

        if (servantRequest.getRequest().env().exception() != null)
		{ constructException(servantRequest); return; }

		// Extract essential content from "ServantRequest":
        String aspName = servantRequest.getAspName();
        long callID = servantRequest.getCallID();

        org.omg.CORBA.Request request = servantRequest.getRequest();
        org.omg.CORBA.NVList nvList = request.arguments();
        org.omg.CORBA.NamedValue result = request.result();

        // Prepare name of Reply PDU:
        StringBuffer buffer = new StringBuffer();
        buffer.append("pREPLY_i");
        buffer.append(aspName.substring(7, aspName.length()));
        String replyAspName = buffer.toString();

		// Initialize Reply PDU:
        asp = new com.t3.ot.pco.ASPimpl(replyAspName);

		// Add Begin of TTCN table tag:
        asp.beginTable(replyAspName);

        // Add CALL_ID field:
        asp.addInteger("CALL_ID", (int) callID); // Modify.

        // Serialize operation response into "DynIterator":
		BuilderAdapter adapter = new BuilderAdapter();
		adapter.create(nvList, result, BuilderAdapter.OPTIONS_SERVER_REPLY);
		DynIterator dynIterator = adapter.iterator();

        // Set initial state of Builder automaton:
		lastStructType = new int[MAX_INTROSPECTION_LEVELS];
		lastStructType[0] = STRUCT_TYPE_NONE;
		isFirstMemberProcessed = false;
        introspectionLevel = 0;
		isExcBody = false;

		if (AnyGeneric.removeAliases(result.value().type()).
			kind().value() != org.omg.CORBA.TCKind._tk_void) {

			isRetValuePresent = true;
		}
		else {
			isRetValuePresent = false;
		}

		// Convert "DynIterator" into Reply PDU, recalling that:

		// -- name of first field ("RET_value") shall be left unchanged;
		// -- "RET_value" field may be absent if its type is "void";
		// -- prefix "PAR_" shall be added to all items of level 0;
		// -- prefix "seq_" shall be added to all members of "struct";
		// -- prefix "ch_" shall be added to all members of "union";
        // -- item name "" usually indicates sequence/array membership.

		if (dynIterator.hasNext()) { addComponent(dynIterator.next()); }
		isFirstMemberProcessed = true;
		while (dynIterator.hasNext()) { addComponent(dynIterator.next()); }

		if (introspectionLevel != 0) {

			System.err.println("ServerReplyBuilder::construct(): " +
                "Internal error -- incorrect Builder state " +
                "(INTROSPECTION_LEVEL).");
		    System.exit(0);
		}

        asp.endTable();
	}

    /** Construct Exception PDU based on information from operation
     *  response provided that Server or ORB have thrown an exception. */
	public void constructException(ServantRequest servantRequest) {

		int i;

		// Extract essential content from "ServantRequest":

        String aspName = servantRequest.getAspName();
        long callID = servantRequest.getCallID();

        org.omg.CORBA.Request request = servantRequest.getRequest();
		Exception ex = request.env().exception();

        // Extract essential content from exception "ex":

		String exName = null;
		String[] exScope = null;
		org.omg.CORBA.Any exBody = null;

		if (ex instanceof org.omg.CORBA.SystemException) {

			exName = ex.getClass().getName();
			StringTokenizer st = new StringTokenizer(exName, ".");
			while (st.hasMoreTokens()) { exName = st.nextToken(); }

			exScope = new String[2];
			exScope[0] = "CORBA";
			exScope[1] = "SystemException";

			exBody = SystemExceptionHelper.except(
				(org.omg.CORBA.SystemException) ex);
		}
		else {
			if (ex instanceof org.omg.CORBA.UserException) {

				org.omg.CORBA.UnknownUserException uex =
					(org.omg.CORBA.UnknownUserException) ex;

				exBody = uex.except;

				org.omg.CORBA.ExceptionDef exDef = null;
				try {
					exDef = Repository.getExceptionDef(exBody.type().id());
				}
				catch (org.omg.CORBA.TypeCodePackage.BadKind bke) {

					System.err.println("ServerReplyBuilder::" +
					    "constructException(): Internal error -- " +
						"unexpected exception \"BadKind\" thrown ");
					System.exit(0);
				}

				if (exDef == null) {

					try {

					System.out.println("ServerReplyBuilder::" +
                        "constructException(): Warning: Unable to " +
                        "retrieve exception with Repository ID \"" +
						exBody.type().id() + "\" from Interface " +
                        "Repository, converting unknown exception " +
						"to CORBA::UNKNOWN");
					}
					catch (org.omg.CORBA.TypeCodePackage.BadKind bkex) { }

					exScope = new String[2];
					exScope[0] = "CORBA";
					exScope[1] = "SystemException";

					exName = "UNKNOWN";

					exBody = SystemExceptionHelper.except(
						new org.omg.CORBA.UNKNOWN());
				}
				else {
					Vector vector = new Vector(5, 5);

					StringTokenizer st = new StringTokenizer(
						exDef.absolute_name(), ":");

					while (st.hasMoreTokens()) { vector.add(st.nextToken()); }

					if (vector.size() == 0) {

						System.err.println("ServerReplyBuilder::" +
						    "constructException(): Internal error -- " +
							"Incorrect definition of exception in " +
							"Interface Repository.");
						System.exit(0);
					}
					String[] array = (String[]) vector.toArray(new String[1]);
					exScope = new String[array.length - 1];

					for (i = 0; i < array.length - 1; i++)
                    { exScope[i] = array[i]; }

					exName = array[array.length - 1];
				}
			}
			else {
				System.err.println("ServerReplyBuilder::constructException()"
					+ ": Internal error -- unrecognized exception thrown " +
		            "on Server side.");
				System.exit(0);
			}
		}

		// At this point we have defined "exScope", "exName" and "exBody":

		// In case "exBody" is empty, we replace it with default exception
		// body according to specification:

        try {
		    if (exBody.type().member_count() == 0) {

				asp = AspConverter.createServerException(
					callID, exScope, exName);
				return;
			}
		}
		catch (org.omg.CORBA.TypeCodePackage.BadKind bkexc) {

			System.err.println("ServerReplyBuilder::" +
				"constructException(): Internal error -- " +
				"unexpected exception \"BadKind\" thrown ");
		    System.exit(0);
		}

        // Perform initial actions so that we could use "BuilderAdapter"
		// in exception handling as easily as possible:

        org.omg.CORBA.Any any = orb.create_any();
		any.type(orb.get_primitive_tc(org.omg.CORBA.TCKind.tk_void));
        org.omg.CORBA.NamedValue result = orb.create_named_value(
			"RET_value", any, org.omg.CORBA.ARG_OUT.value);

		org.omg.CORBA.NVList nvList = orb.create_list(1);
		nvList.add_value("EXC_BODY", exBody, org.omg.CORBA.ARG_OUT.value);

		// Initialize Exception PDU:
        asp = new com.t3.ot.pco.ASPimpl("pRAISE");
        asp.beginTable("pRAISE");

        asp.addInteger("CALL_ID", (int) callID); // Modify

		// Add "EXC_SCOPE" field:
        asp.beginSequenceOf("EXC_SCOPE");
        for (i = 0; i < exScope.length; i++)
        { asp.addCharString("$unnamed", exScope[i]); }
        asp.endSequenceOf();

		// Add "EXC_NAME" field:
        asp.addCharString("EXC_NAME", exName);

        // Add "EXC_BODY" field:
        asp.beginTable("EXC_BODY");

        // Serialize operation response into "DynIterator":
		BuilderAdapter adapter = new BuilderAdapter();
		adapter.create(nvList, result, BuilderAdapter.OPTIONS_SERVER_REPLY);
		DynIterator dynIterator = adapter.iterator();

        // Set initial state of Builder automaton:
		lastStructType = new int[MAX_INTROSPECTION_LEVELS];
		lastStructType[0] = STRUCT_TYPE_NONE;
		isFirstMemberProcessed = false;
		isRetValuePresent = false;
        introspectionLevel = 0;
		isExcBody = true;

		// Convert "DynIterator" into body of Exception PDU:

		if (dynIterator.hasNext()) { addComponent(dynIterator.next()); }
		isFirstMemberProcessed = true;
		while (dynIterator.hasNext()) { addComponent(dynIterator.next()); }

		if (introspectionLevel != 0) {

			System.err.println("ServerReplyBuilder::constructException(): " +
                "Internal error -- incorrect Builder state " +
                "(INTROSPECTION_LEVEL).");
		    System.exit(0);
		}

        asp.endTable();
        asp.endTable();
	}

	/** Add prefix to field name, according to current Builder state. */
	protected String addPrefix(DynNode dynNode) {

        String name = dynNode.name();

		if (isRetValuePresent == true) {

			if (isFirstMemberProcessed == false) {

				if (name.equals("RET_value") == false) {

					System.err.println("ServerReplyBuilder::addPrefix(): " +
                        "Internal error -- incorrect Builder state " +
                        "(RET_value).");
					System.exit(0);
				}
				else return name;
			}
		}

		if (introspectionLevel < 0) {
			System.err.println("ServerReplyBuilder::addPrefix(): " +
                "Internal error -- incorrect Builder state " +
                "(INTROSPECTION_LEVEL).");
			System.exit(0);
		}

		switch (lastStructType[introspectionLevel]) {

			case STRUCT_TYPE_UNION:

				return ("ch_" + name);

            case STRUCT_TYPE_STRUCT:

				return ("seq_" + name);

            case STRUCT_TYPE_SEQUENCE:
            case STRUCT_TYPE_ARRAY:

		        int type = AnyGeneric.removeAliases(
					dynNode.value().type()).kind().value();

				if ((type == org.omg.CORBA.TCKind._tk_boolean) ||
                    (type == org.omg.CORBA.TCKind._tk_char) ||
					(type == org.omg.CORBA.TCKind._tk_long) ||
					(type == org.omg.CORBA.TCKind._tk_longlong) ||
					(type == org.omg.CORBA.TCKind._tk_octet) ||
					(type == org.omg.CORBA.TCKind._tk_short) ||
					(type == org.omg.CORBA.TCKind._tk_string) ||
					(type == org.omg.CORBA.TCKind._tk_ulong) ||
					(type == org.omg.CORBA.TCKind._tk_ulonglong) ||
					(type == org.omg.CORBA.TCKind._tk_ushort) ||
					(type == org.omg.CORBA.TCKind._tk_enum)) {

					return "$unnamed";
				}
				else {

					if ((type == org.omg.CORBA.TCKind._tk_array) ||
						(type == org.omg.CORBA.TCKind._tk_sequence) ||
						(type == org.omg.CORBA.TCKind._tk_struct) ||
						(type == org.omg.CORBA.TCKind._tk_except) ||
						(type == org.omg.CORBA.TCKind._tk_union)) {

						return "$typed_composite";
					}
					else {

						System.err.println("ServerReplyBuilder::addPrefix(): "
							+ "Data type of operation parameter is currently "
							+ "not supported.");
						System.exit(0);
					}
				}

            case STRUCT_TYPE_NONE:

				if ((introspectionLevel == 0) && (!isExcBody))
				{ return ("PAR_" + name); } else { return name; }

			default:

				System.err.println("ServerReplyBuilder::addPrefix(): " +
					"Internal error -- incorrect Builder state " +
                    "(STRUCT_TYPE).");
			    System.exit(0);
		}
		return null;
	}

	/** Perform next step of PDU construction process. */
    protected void addComponent(DynNode dynNode) {

        org.omg.CORBA.Any any = dynNode.value();
		String name = addPrefix(dynNode);

		org.omg.CORBA.DynAny dynAny;
        String characterString;
		String octetString;
        long longValue;

        try {

        switch (AnyGeneric.removeAliases(any.type()).kind().value()) {

        case org.omg.CORBA.TCKind._tk_boolean:
            asp.addBoolean(name, any.extract_boolean());
            break;

        case org.omg.CORBA.TCKind._tk_char:
            char[] characterArray = new char[1];
            characterArray[0] = any.extract_char();
            characterString = new String(characterArray);
            asp.addCharString(name, characterString);
            break;

        case org.omg.CORBA.TCKind._tk_long:
            asp.addInteger(name, 
                /*(long)*/ (int) any.extract_long());
            break;

        case org.omg.CORBA.TCKind._tk_longlong:
            asp.addInteger(name, 
                /*(long)*/ (int) any.extract_longlong());
            break;

        case org.omg.CORBA.TCKind._tk_octet:

			byte[] byteArray = new byte[1];
			byteArray[0] = any.extract_octet();
			octetString = com.t3.ot.misc.Convert.
				byteArrayToOctetString(byteArray);
			asp.addOctetString(name, octetString);
            break;

        case org.omg.CORBA.TCKind._tk_short:
            asp.addInteger(name, 
                /* (long) */ (int) any.extract_short());
            break;

        case org.omg.CORBA.TCKind._tk_string:
            asp.addCharString(name, any.extract_string());
            break;

        case org.omg.CORBA.TCKind._tk_ulong:
            asp.addInteger(name, (int) /* remove */
                (((long) any.extract_ulong()) & 0xFFFFFFFF));
            break;

        case org.omg.CORBA.TCKind._tk_ulonglong:
            asp.addInteger(name, 
                /* (long) */ (int) any.extract_ulonglong());
            break;

        case org.omg.CORBA.TCKind._tk_ushort:
            asp.addInteger(name, (int) /* remove */
                (((long) any.extract_ushort()) & 0xFFFF));
            break;

		case org.omg.CORBA.TCKind._tk_array:
			if (dynNode.flags() == DynNode.FLAGS_BEG_OF_NODE) {

				introspectionLevel++;
				lastStructType[introspectionLevel] = STRUCT_TYPE_ARRAY;
				asp.beginSequenceOf(name);
			}
			else if (dynNode.flags() == DynNode.FLAGS_END_OF_NODE) {

				introspectionLevel--;
				asp.endSequenceOf();
			}
			else {
				System.err.println("ServerReplyBuilder::addComponent(): " +
					"Internal error -- incorrect Builder state " +
                    "(_tk_array).");
				System.exit(0);
			}
			break;

        case org.omg.CORBA.TCKind._tk_enum:

            org.omg.CORBA.DynEnum dynEnum = null;

			dynAny = orb.create_dyn_any(any);
			dynEnum = org.omg.CORBA.DynEnumHelper.narrow(dynAny);

			if (dynEnum == null) {
				System.err.println("ServerReplyBuilder::addComponent(): " +
                    "Internal error -- null pointer (_tk_enum).");
				System.exit(0);
			}
			asp.addEnumerated(name, (int) /* remove */
               (((long) dynEnum.value_as_ulong()) & 0xFFFFFFFF));
			break;

        case org.omg.CORBA.TCKind._tk_sequence:
			if (dynNode.flags() == DynNode.FLAGS_BEG_OF_NODE) {

				introspectionLevel++;
				lastStructType[introspectionLevel] = STRUCT_TYPE_SEQUENCE;
				asp.beginSequenceOf(name);
			}
			else if (dynNode.flags() == DynNode.FLAGS_END_OF_NODE) {

				introspectionLevel--;
				asp.endSequenceOf();
			}
			else {
				System.err.println("ServerReplyBuilder::addComponent(): " +
					"Internal error -- incorrect Builder state " +
                    "(_tk_sequence).");
				System.exit(0);
			}
			break;

        case org.omg.CORBA.TCKind._tk_except:
        case org.omg.CORBA.TCKind._tk_struct:
            if (dynNode.flags() == DynNode.FLAGS_BEG_OF_NODE) {

				introspectionLevel++;
				lastStructType[introspectionLevel] = STRUCT_TYPE_STRUCT;
				asp.beginSequence(name);
			}
			else if (dynNode.flags() == DynNode.FLAGS_END_OF_NODE) {

				introspectionLevel--;
				asp.endSequence();
			}
			else {
				System.err.println("ServerReplyBuilder::addComponent(): " +
					"Internal error -- incorrect Builder state " +
                    "(_tk_struct).");
				System.exit(0);
			}
			break;

        case org.omg.CORBA.TCKind._tk_union:
			if (dynNode.flags() == DynNode.FLAGS_BEG_OF_NODE) {

				introspectionLevel++;
				lastStructType[introspectionLevel] = STRUCT_TYPE_UNION;
                asp.beginTable(name); /* Modify to: asp.beginChoice(name); */
			}
			else if (dynNode.flags() == DynNode.FLAGS_END_OF_NODE) {

				introspectionLevel--;
                asp.endTable(); /* Modify to: asp.endChoice(); */
			}
			else {
				System.err.println("ServerReplyBuilder::addComponent(): " +
					"Internal error -- incorrect Builder state " +
                    "(_tk_union).");
				System.exit(0);
			}
			break;
            
        default:
            System.err.println("ServerReplyBuilder::addComponent(): " +
                "Data type of operation parameter is currently not " +
                "supported.");
            System.exit(0);
        }
        }
        catch (org.omg.CORBA.BAD_OPERATION ex) {
            System.err.println("ServerReplyBuilder::addComponent(): " +
                "CORBA::BAD_OPERATION exception thrown.");
            System.exit(0);
        }
    }
}
