/*
 *  BuilderAdapter.java v0.10 20-DEC-1999
 *  Copyright (c) TKK/TLM/Calypso
 *  Author: Alexey Mednonogov
 */

package codec.adapt;

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 performing adaptation of CORBA NVList introspection capabilities
 *  to requirements of e.g. ServerReplyBuilder. Ordered collection of objects
 *  is created so that it can be directly employed by Builder. Resulting
 *  collection can be accessed as DynIterator. */
final public class BuilderAdapter extends CorbaTtcnAdapter {

   	private Vector vector;
   	private org.omg.CORBA.ORB orb;

   	// Have we just encapsulated DynUnion into DynStruct or not yet.
   	// Influences behaviour of "_tk_union" branch of "introspect()":
   	private boolean isUnionEncapsulated;

	private org.omg.CORBA.DynUnion dynUnion_;

   	/** Perform adaptation for Server Reply PDU. */
   	public static final int OPTIONS_SERVER_REPLY  = 0;
    /** Perform adaptation for Client Call PDU. */
   	public static final int OPTIONS_CLIENT_CALL = 1;

	public DynIterator iterator() {
		return new DynIterator(vector.iterator());
	}

   	/** Introspect unnamed components of DynArray, DynSequence. */
   	private void introspectComponents(org.omg.CORBA.DynAny dynAny) {

		try {
		if (dynAny.seek(0) == false) {

			if (AnyGeneric.removeAliases(dynAny.to_any().type()).kind()
                != org.omg.CORBA.TCKind.tk_sequence) {

				// Arrays must always be of non-zero length:
				String kind = "";

				kind = Debug.tcKindToString(AnyGeneric.
                    removeAliases(dynAny.to_any().type()).kind());

				System.err.println("BuilderAdapter::introspectComponents(): "
					+ "An array has been detected that contains no fields ("
					+ kind + ").");
				System.exit(0);
			}
			else {
				// Sequences are allowed to have zero length at run-time.
				return;
			}
		}

		dynAny.rewind();

		do {
		   	org.omg.CORBA.Any any = dynAny.current_component().to_any();
		   	introspect(new org.omg.CORBA.NameValuePair("", any));

		} while (dynAny.next());
		}
		catch (org.omg.CORBA.DynAnyPackage.Invalid ex) {
			System.err.println("BuilderAdapter::introspectComponents(): "
				+ "Internal error -- CORBA Invalid exception.");
			System.exit(0);
		}
	}

	/** Introspect named components of DynStruct. */
    private void introspectStruct(org.omg.CORBA.DynStruct dynStruct) {

        try {
		if (dynStruct.seek(0) == false) {

			String kind = "";

			kind = Debug.tcKindToString(AnyGeneric.
                removeAliases(dynStruct.to_any().type()).kind());

			System.err.println("BuilderAdapter::introspectStruct(): " +
                "A structure has been detected that contains no fields (" +
				kind + ").");
			System.exit(0);
		}

		dynStruct.rewind();

		do {
		   	org.omg.CORBA.Any any = dynStruct.current_component().to_any();

		   	introspect(new org.omg.CORBA.NameValuePair(
				dynStruct.current_member_name(), any));

		} while (dynStruct.next());
		}
		catch (org.omg.CORBA.DynAnyPackage.Invalid ex) {
			System.err.println("BuilderAdapter::introspectStruct(): "
				+ "Internal error -- CORBA Invalid exception.");
			System.exit(0);
		}
	}

   	private void introspect(org.omg.CORBA.NameValuePair pair) {

		int i;

		org.omg.CORBA.DynAny dynAny = null;
		org.omg.CORBA.Any any = null;
		DynNode dynNode = null;

		org.omg.CORBA.TypeCode code = pair.value.type();
		code = AnyGeneric.removeAliases(code);

        switch (code.kind().value()) {

        case org.omg.CORBA.TCKind._tk_boolean:
        case org.omg.CORBA.TCKind._tk_char:
        case org.omg.CORBA.TCKind._tk_long:
        case org.omg.CORBA.TCKind._tk_longlong:
        case org.omg.CORBA.TCKind._tk_octet:
        case org.omg.CORBA.TCKind._tk_short:
        case org.omg.CORBA.TCKind._tk_string:
        case org.omg.CORBA.TCKind._tk_ulong:
        case org.omg.CORBA.TCKind._tk_ulonglong:
        case org.omg.CORBA.TCKind._tk_ushort:

			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_LEAF));
            break;

   		case org.omg.CORBA.TCKind._tk_array:
			
			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_BEG_OF_NODE));

			org.omg.CORBA.DynArray dynArray = null;

			dynAny = orb.create_dyn_any(pair.value);
			dynArray = org.omg.CORBA.DynArrayHelper.narrow(dynAny);

			if (dynArray == null) {
				System.err.println("BuilderAdapter::introspect(): " +
                    "Internal error -- null pointer (_tk_array).");
				System.exit(0);
			}
			introspectComponents(dynArray);

			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_END_OF_NODE));
            break;

        case org.omg.CORBA.TCKind._tk_enum:

			// Handling "enum" is somehow complicated since OpenTTCN seem
			// to support it as a pure INTEGER while CORBA treats it as
			// structured type. To provide compatibility with Builder,
			// "enum" value is treated as a primitive type, i.e. it will be
			// iterated as a single DynNode with "flags" set to "FLAGS_LEAF",
			// burdening the Builder with extracting "DynEnum" and handling it
			// by itself:
            
			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_LEAF));
            break;

   		case org.omg.CORBA.TCKind._tk_sequence:

            vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_BEG_OF_NODE));

			org.omg.CORBA.DynSequence dynSequence = null;

			dynAny = orb.create_dyn_any(pair.value);
			dynSequence = org.omg.CORBA.DynSequenceHelper.narrow(dynAny);

			if (dynSequence == null) {
				System.err.println("BuilderAdapter::introspect(): " +
                    "Internal error -- null pointer (_tk_sequence).");
				System.exit(0);
			}
			introspectComponents(dynSequence);

			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_END_OF_NODE));			
            break;

		case org.omg.CORBA.TCKind._tk_except:
        case org.omg.CORBA.TCKind._tk_struct:
			
			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_BEG_OF_NODE));

			org.omg.CORBA.DynStruct dynStruct = null;

            dynAny = orb.create_dyn_any(pair.value);
			dynStruct = org.omg.CORBA.DynStructHelper.narrow(dynAny);

			if (dynStruct == null) {
				System.err.println("BuilderAdapter::introspect(): " +
                    "Internal error -- null pointer (_tk_struct).");
				System.exit(0);
			}
			introspectStruct(dynStruct);

			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_END_OF_NODE));
			
            break;

		case org.omg.CORBA.TCKind._tk_union:

			if (isUnionEncapsulated == false) {

				// Before handling "union", we encapsulate it into "struct"
				// containing discriminator according to specification:

				// Save DynUnion to (global) class field:
				dynUnion_ = null;

                dynAny = orb.create_dyn_any(pair.value);
				dynUnion_ = org.omg.CORBA.DynUnionHelper.narrow(dynAny);

				if (dynUnion_ == null) {
					System.err.println("BuilderAdapter::introspect(): " +
                        "Internal error -- null pointer (_tk_union).");
					System.exit(0);
				}

				// Create instance of "UnionStruct":
				org.omg.CORBA.TypeCode tc = UnionStructHelper.type(
					dynUnion_.discriminator().type(), dynUnion_.type());

				// Initialize contents of "UnionStruct":
                org.omg.CORBA.DynStruct dynStruct_ = null;
				try {
					dynStruct_ = orb.create_dyn_struct(tc);
				}
				catch (org.omg.CORBA.ORBPackage.InconsistentTypeCode itc) {
					System.err.println("BuilderAdapter::introspect(): " +
					    "Internal error -- CORBA exception (_tk_union).");
					System.exit(0);
				}

				if (dynStruct_ == null) {
				    System.err.println("BuilderAdapter::introspect(): " +
                        "Internal error -- null pointer (_tk_union).");
					System.exit(0);
				}

				dynStruct_.rewind();
				org.omg.CORBA.Any anyStruct = null;
				try {
					org.omg.CORBA.DynAny discr = dynUnion_.discriminator();
					dynStruct_.current_component().assign(discr);

					dynStruct_.next();
					dynStruct_.current_component().assign(dynUnion_);
				
				    // Create "Any" from properly initialized "UnionStruct":
				    anyStruct = dynStruct_.to_any();
				}
				catch (org.omg.CORBA.DynAnyPackage.Invalid ie) {
					System.err.println("BuilderAdapter::introspect(): " +
					    "Internal error -- Invalid exception (_tk_union).");
					System.exit(0);
				}

				isUnionEncapsulated = true;

				introspect(new org.omg.CORBA.NameValuePair(
					pair.id, anyStruct));
			}
			else {
				// Here we are on deeper level of introspection. "Union" is
				// already encapsulated into "struct":
				
				isUnionEncapsulated = false;

				vector.add(new DynNode(
					pair.id, pair.value, DynNode.FLAGS_BEG_OF_NODE));

				try {
					introspect(new org.omg.CORBA.NameValuePair(
				        dynUnion_.member_name(), dynUnion_.member().to_any()));
				}
				catch (org.omg.CORBA.DynAnyPackage.Invalid ie) {
					System.err.println("BuilderAdapter::introspect(): " +
					    "Internal error -- Invalid exception (_tk_union).");
					System.exit(0);
				}
				
				vector.add(new DynNode(
					pair.id, pair.value, DynNode.FLAGS_END_OF_NODE));

			}
            break;

		case org.omg.CORBA.TCKind._tk_void:
			// If return value of operation is of type "void", then it is
			// skipped in the corresponding PDU. If anything other than
            // "RET_value" is here, such situation is treated as error:
			if (pair.id.equals("RET_value") == false) {
				System.err.println("BuilderAdapter::introspect(): " +
                    "Internal error -- only \"RET_value\" is allowed " +
                    "to be of type \"void\".");
				System.exit(0);
			}
			break;

        default:
            System.err.println("BuilderAdapter::introspect(): " +
                "Data type not supported (\"" + pair.id + "\" : " + 
				Debug.tcKindToString(code.kind()) + ").");
            System.exit(0);
		}
	}

   	/** Create OpenTTCN-compatible model of structured data from input.
   	 *  Parameter "result" shall be set to "null" in case "options" are set
   	 *  to value different from OPTIONS_SERVER_REPLY, otherwise it shall
   	 *  point to the result of the operation. */
   	public void create(org.omg.CORBA.NVList nvList,
		org.omg.CORBA.NamedValue result, int options) {

		vector = new Vector(100, 100);
		orb = CorbaServer.getCodecObject().getOrbGeneric().getORB();
		isUnionEncapsulated = false;

		if (options == OPTIONS_SERVER_REPLY) {

			introspect(new org.omg.CORBA.NameValuePair(
				"RET_value", result.value()));
		}

		for (int i = 0; i < nvList.count(); i++) {

   			org.omg.CORBA.NamedValue namedValue = null;
			try {
                namedValue = nvList.item(i);
            }
            catch (org.omg.CORBA.Bounds ex) {

                System.err.println("BuilderAdapter::create(): " +
					"Index out of bounds.");
                System.exit(0);
            }

			switch (options) {

   			case OPTIONS_CLIENT_CALL:
				if ((namedValue.flags() != org.omg.CORBA.ARG_IN.value) &&
					(namedValue.flags() != org.omg.CORBA.ARG_INOUT.value)) {
					continue;
				}
				break;

			case OPTIONS_SERVER_REPLY:
				if ((namedValue.flags() != org.omg.CORBA.ARG_OUT.value) &&
					(namedValue.flags() != org.omg.CORBA.ARG_INOUT.value)) {
					continue;
				}
				break;

   			default:
				System.err.println("BuilderAdapter::create(): " +
					"Internal error.");
                System.exit(0);
			}

			introspect(new org.omg.CORBA.NameValuePair(
				namedValue.name(), namedValue.value()));
		}
	}
}
