/*****************************************************************
JADE - Java Agent DEvelopment Framework is a framework to develop 
multi-agent systems in compliance with the FIPA specifications.
Copyright (C) 2000 CSELT S.p.A. 

The updating of this file to JADE 2.0 has been partially supported by the IST-1999-10211 LEAP Project

GNU Lesser General Public License

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, 
version 2.1 of the License. 

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA  02111-1307, USA.
*****************************************************************/

package jade.mtp.iiop;


import java.io.*;

import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Date;
import java.util.Calendar;
import java.util.StringTokenizer;
import java.util.NoSuchElementException;

import org.omg.CORBA.*;
import org.omg.CosNaming.*;

import FIPA.*; // OMG IDL Stubs

import jade.core.AID;
import jade.core.Profile;

import jade.mtp.InChannel;
import jade.mtp.OutChannel;
import jade.mtp.MTP;
import jade.mtp.MTPException;
import jade.mtp.TransportAddress;
import jade.domain.FIPAAgentManagement.Envelope;
import jade.domain.FIPAAgentManagement.ReceivedObject;
import jade.domain.FIPAAgentManagement.Property;
//FIXME: if we add the method getSize() to all the slots whose value is a set
// (e.g. intendedReceiver, userdefProperties, ...) we can improve a lot
// performance in marshalling and unmarshalling.

/**
   Implementation of <code><b>fipa.mts.mtp.iiop.std</b></code>
   specification for delivering ACL messages over the OMG IIOP
   transport protocol.

   @author Giovanni Rimassa - Universita' di Parma
   @version $Date: 2008-10-09 14:04:02 +0200 (gio, 09 ott 2008) $ $Revision: 6051 $
 */
public class MessageTransportProtocol implements MTP {


  private static class MTSImpl extends FIPA._MTSImplBase {

    private final InChannel.Dispatcher dispatcher;

    public MTSImpl(InChannel.Dispatcher disp) {
      dispatcher = disp;
    }

    public void message(FipaMessage aFipaMessage) {
      FIPA.Envelope[] envelopes = aFipaMessage.messageEnvelopes;
      byte[] payload = aFipaMessage.messageBody;
      
      Envelope env = new Envelope();

      // Read all the envelopes sequentially, so that later slots
      // overwrite earlier ones.
      for(int e = 0; e < envelopes.length; e++) {
	FIPA.Envelope IDLenv = envelopes[e];

	// Read in the 'to' slot
	if(IDLenv.to.length > 0)
	  env.clearAllTo();
	for(int i = 0; i < IDLenv.to.length; i++) {
	  AID id = unmarshalAID(IDLenv.to[i]);
	  env.addTo(id);
	}

	// Read in the 'from' slot
	if(IDLenv.from.length > 0) {
	  AID id = unmarshalAID(IDLenv.from[0]);
	  env.setFrom(id);
	}

	// Read in the 'intended-receiver' slot
	if(IDLenv.intendedReceiver.length > 0)
	  env.clearAllIntendedReceiver();
	for(int i = 0; i < IDLenv.intendedReceiver.length; i++) {
	  AID id = unmarshalAID(IDLenv.intendedReceiver[i]);
	  env.addIntendedReceiver(id);
	}

	// Read in the 'encrypted' slot
	//if(IDLenv.encrypted.length > 0)
	//  env.clearAllEncrypted();
	//for(int i = 0; i < IDLenv.encrypted.length; i++) {
	//  String word = IDLenv.encrypted[i];
	//  env.addEncrypted(word);
	//}

	// Read in the other slots
	if(IDLenv.comments.length() > 0)
	  env.setComments(IDLenv.comments);
	if(IDLenv.aclRepresentation.length() > 0)
	  env.setAclRepresentation(IDLenv.aclRepresentation);
	if(IDLenv.payloadLength > 0)
	  env.setPayloadLength(new Long(IDLenv.payloadLength));
	if(IDLenv.payloadEncoding.length() > 0)
	  env.setPayloadEncoding(IDLenv.payloadEncoding);
	if(IDLenv.date.length > 0) {
	  Date d = unmarshalDateTime(IDLenv.date[0]);
	  env.setDate(d);
	}

	// Read in the 'received' stamp
	if(IDLenv.received.length > 0)
	  env.addStamp(unmarshalReceivedObj(IDLenv.received[0]));

	// Read in the 'user-defined properties' slot
	if(IDLenv.userDefinedProperties.length > 0)
	  env.clearAllProperties();
	for(int i = 0; i < IDLenv.userDefinedProperties.length; i++) {
	  env.addProperties(unmarshalProperty(IDLenv.userDefinedProperties[i]));
	}
      }
      
      //String tmp = "\n\n"+(new  java.util.Date()).toString()+"   RECEIVED IIOP MESSAGE"+ "\n" + env.toString() + "\n" + new String(payload); 
      //System.out.println(tmp);
   
       
      //MessageTransportProtocol.log(tmp); //Write in a log file for iiop incoming message
      
      // Dispatch the message
      dispatcher.dispatchMessage(env, payload);

    }


    private AID unmarshalAID(FIPA.AgentID id) {
      AID result = new AID();
      result.setName(id.name);
      for(int i = 0; i < id.addresses.length; i++)
	result.addAddresses(id.addresses[i]);
      for(int i = 0; i < id.resolvers.length; i++)
	result.addResolvers(unmarshalAID(id.resolvers[i]));
      return result;
    }

    private Date unmarshalDateTime(FIPA.DateTime d) {
      Date result = new Date();
      return result;
    }

    private Property unmarshalProperty(FIPA.Property p) {
      return new Property(p.keyword, p.value.extract_Value());
    }

    private ReceivedObject unmarshalReceivedObj(FIPA.ReceivedObject ro) {
      ReceivedObject result = new ReceivedObject();
      result.setBy(ro.by);
      result.setFrom(ro.from);
      result.setDate(unmarshalDateTime(ro.date));
      result.setId(ro.id);
      result.setVia(ro.via);
      return result;
    }

  } // End of MTSImpl class


  private static final String[] PROTOCOLS = new String[] { "IOR", "corbaloc", "corbaname" };

  private final ORB myORB;
  private MTSImpl server;
  private static PrintWriter logFile;
  
  public MessageTransportProtocol() {
    myORB = ORB.init(new String[0], null);
    
  }

  public TransportAddress activate(InChannel.Dispatcher disp, Profile p) throws MTPException {
    server = new MTSImpl(disp);
    myORB.connect(server);
    IIOPAddress iiop = new IIOPAddress(myORB, server);

    /* //Open log file
    String fileName = "iiop"+iiop.getHost()+iiop.getPort()+".log";
		try{
    	logFile = new PrintWriter(new FileWriter(fileName,true));
    }catch(java.io.IOException e){e .printStackTrace();}
    */
    return iiop;
    
  }

  public void activate(InChannel.Dispatcher disp, TransportAddress ta, Profile p) throws MTPException {
      //    throw new MTPException("User supplied transport address not supported.");

      // Do not throw, but modify the supplied address instead.
      IIOPAddress iia = (IIOPAddress)ta;
      IIOPAddress generated = (IIOPAddress)activate(disp, p);
      iia.initFromIOR(generated.getIOR());
  }

  public void deactivate(TransportAddress ta) throws MTPException {
    myORB.disconnect(server);
  }

  public void deactivate() throws MTPException {
    myORB.disconnect(server);
  }

  public void deliver(String addr, Envelope env, byte[] payload) throws MTPException {
    try {
      TransportAddress ta = strToAddr(addr);
      IIOPAddress iiopAddr = (IIOPAddress)ta;
      FIPA.MTS objRef = iiopAddr.getObject();

      // verifies if the server object really exists (useful if the IOR is
      // valid, i.e corresponds to a good object) (e.g. old IOR)
      // FIXME. To check if this call slows down performance
      if (objRef._non_existent()) 
        throw new MTPException("Bad IIOP server object reference:" + objRef.toString());

      // Fill in the 'to' field of the IDL envelope
      Iterator itTo = env.getAllTo();
      List to = new ArrayList();
      while(itTo.hasNext()) {
	AID id = (AID)itTo.next();
	to.add(marshalAID(id));
      }

      FIPA.AgentID[] IDLto = new FIPA.AgentID[to.size()];
      for(int i = 0; i < to.size(); i++)
	IDLto[i] = (FIPA.AgentID)to.get(i);


      // Fill in the 'from' field of the IDL envelope
      AID from = env.getFrom();
      FIPA.AgentID[] IDLfrom = new FIPA.AgentID[] { marshalAID(from) };


      // Fill in the 'intended-receiver' field of the IDL envelope
      Iterator itIntendedReceiver = env.getAllIntendedReceiver();
      List intendedReceiver = new ArrayList();
      while(itIntendedReceiver.hasNext()) {
	AID id = (AID)itIntendedReceiver.next();
	intendedReceiver.add(marshalAID(id));
      }

      FIPA.AgentID[] IDLintendedReceiver = new FIPA.AgentID[intendedReceiver.size()];
      for(int i = 0; i < intendedReceiver.size(); i++)
	IDLintendedReceiver[i] = (FIPA.AgentID)intendedReceiver.get(i);


      // Fill in the 'encrypted' field of the IDL envelope
      //Iterator itEncrypted = env.getAllEncrypted();
      //List encrypted = new ArrayList();
      //while(itEncrypted.hasNext()) {
	//String word = (String)itEncrypted.next();
	//encrypted.add(word);
      //}

      String[] IDLencrypted = new String[0];
      //String[] IDLencrypted = new String[encrypted.size()];
      //for(int i = 0; i < encrypted.size(); i++)
	//IDLencrypted[i] = (String)encrypted.get(i);


      // Fill in the other fields of the IDL envelope ...
      
      String IDLcomments = (env.getComments() != null)?env.getComments():"";
      String IDLaclRepresentation = env.getAclRepresentation();
      Long payloadLength = env.getPayloadLength();
      int IDLpayloadLength = payloadLength.intValue();
      String IDLpayloadEncoding = (env.getPayloadEncoding() != null)?env.getPayloadEncoding():"";
      FIPA.DateTime[] IDLdate = new FIPA.DateTime[] { marshalDateTime(env.getDate()) };
      FIPA.Property[][] IDLtransportBehaviour = new FIPA.Property[][] { };

      // Fill in the 'userdefined-properties' field of the IDL envelope
      Iterator itUserDefProps = env.getAllProperties();
      List userDefProps = new ArrayList();
      while(itUserDefProps.hasNext()) {
	Property p = (Property)itUserDefProps.next();
	userDefProps.add(marshalProperty(p));
      }
      FIPA.Property[] IDLuserDefinedProperties = new FIPA.Property[userDefProps.size()];
      for(int i = 0; i < userDefProps.size(); i++)
	IDLuserDefinedProperties[i] = (FIPA.Property)userDefProps.get(i);

      // Fill in the list of 'received' stamps
      /* FIXME: Maybe several IDL Envelopes should be generated, one for every 'received' stamp...
      ReceivedObject[] received = env.getStamps();
      FIPA.ReceivedObject[] IDLreceived = new FIPA.ReceivedObject[received.length];
      for(int i = 0; i < received.length; i++)
	IDLreceived[i] = marshalReceivedObj(received[i]);
      */

      // FIXME: For now, only the current 'received' object is considered...
      ReceivedObject received = env.getReceived();
      FIPA.ReceivedObject[] IDLreceived;
      if(received != null)
	IDLreceived = new FIPA.ReceivedObject[] { marshalReceivedObj(received) };
      else
	IDLreceived = new FIPA.ReceivedObject[] { };

      FIPA.Envelope IDLenv = new FIPA.Envelope(IDLto,
					       IDLfrom,
					       IDLcomments,
					       IDLaclRepresentation,
					       IDLpayloadLength,
					       IDLpayloadEncoding,
					       IDLdate,
					       IDLencrypted,
					       IDLintendedReceiver,
					       IDLreceived,
					       IDLtransportBehaviour,
					       IDLuserDefinedProperties);

      FipaMessage msg = new FipaMessage(new FIPA.Envelope[] { IDLenv }, payload);

      //String tmp = "\n\n"+(new  java.util.Date()).toString()+"   SENT IIOP MESSAGE"+ "\n" + env.toString() + "\n" + new String(payload); 
      //System.out.println(tmp);
	
  //MessageTransportProtocol.log(tmp); // write in a log file for sent iiop message
		
	objRef.message(msg);
    }
    catch(ClassCastException cce) {
      cce.printStackTrace();
      throw new MTPException("Address mismatch: this is not a valid IIOP address.");
    }
 catch(Exception cce2) {
      cce2.printStackTrace();
      throw new MTPException("Address mismatch: this is not a valid IIOP address.");
    }

  }

  public TransportAddress strToAddr(String rep) throws MTPException {
    return new IIOPAddress(myORB, rep); // FIXME: Should cache object references
  }

  public String addrToStr(TransportAddress ta) throws MTPException {
    try {
      IIOPAddress addr = (IIOPAddress)ta;
      return addr.getIOR();
    }
    catch(ClassCastException cce) {
      throw new MTPException("Address mismatch: this is not a valid IIOP address.");
    }
  }

  public String getName() {
    return jade.domain.FIPANames.MTP.IIOP; 
  }

  public String[] getSupportedProtocols() {
    return PROTOCOLS;
  }

  private FIPA.Property marshalProperty(jade.domain.FIPAAgentManagement.Property p) {
  	org.omg.CORBA.Any value = myORB.create_any();
  	java.lang.Object v = p.getValue();
  	if (v instanceof java.io.Serializable) {
    	value.insert_Value((Serializable) v);
  	}
  	else {
  		if (v != null) {
	  		value.insert_Value(v.toString());
  		}
  	}
    return new FIPA.Property(p.getName(), value);
  }

  private FIPA.AgentID marshalAID(AID id) {
    String name = id.getName();
    String[] addresses = id.getAddressesArray();
    AID[] resolvers = id.getResolversArray();
    FIPA.Property[] userDefinedProperties = new FIPA.Property[] { };
    int numOfResolvers = resolvers.length;
    FIPA.AgentID result = new FIPA.AgentID(name, addresses, new AgentID[numOfResolvers], userDefinedProperties);
    for(int i = 0; i < numOfResolvers; i++) {
      result.resolvers[i] = marshalAID(resolvers[i]); // Recursively marshal all resolvers, which are, in turn, AIDs.
    }

    return result;

  }

  private FIPA.DateTime marshalDateTime(Date d) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(d);
    short year = (short)cal.get(Calendar.YEAR);
    short month = (short)cal.get(Calendar.MONTH);
    short day = (short)cal.get(Calendar.DAY_OF_MONTH);
    short hour = (short)cal.get(Calendar.HOUR_OF_DAY);
    short minutes = (short)cal.get(Calendar.MINUTE);
    short seconds = (short)cal.get(Calendar.SECOND);
    short milliseconds = 0; // FIXME: This is truncated to the second
    char typeDesignator = ' '; // FIXME: Uses local timezone ?
    FIPA.DateTime result = new FIPA.DateTime(year,
					     month,
					     day,
					     hour,
					     minutes,
					     seconds,
					     milliseconds,
					     typeDesignator);
    return result;
  }

  private FIPA.ReceivedObject marshalReceivedObj(ReceivedObject ro) {
    FIPA.ReceivedObject result = new FIPA.ReceivedObject();
    result.by = ro.getBy();
    result.from = ro.getFrom();
    result.date = marshalDateTime(ro.getDate());
    result.id = ro.getId();
    result.via = ro.getVia();
    return result;
  }
 

//Method to write on a file the iiop message log file
/*public static synchronized void log(String str) {
  logFile.println(str);  
	logFile.flush();
		}*/

} // End of class MessageTransportProtocol

/**
This class represents an IIOP address.
Three syntaxes are allowed for an IIOP address (all case-insensitive):
<code>
IIOPAddress ::= "ior:" (HexDigit HexDigit+)
              | "corbaname://" NSHost ":" NSPort "/" NSObjectID "#" objectName
              | "corbaloc:" HostName ":" portNumber "/" objectID
</code>
Notice that, in the third case, BIG_ENDIAN is assumed by default. In the first and second case, instead, the endianess information is contained within the IOR definition.
**/
  class IIOPAddress implements TransportAddress {

    public static final byte BIG_ENDIAN = 0;
    public static final byte LITTLE_ENDIAN = 1;

    private static final String FIPA_2000_TYPE_ID = "IDL:FIPA/MTS:1.0";
    private static final String NS_TYPE_ID = "IDL:omg.org/CosNaming/NamingContext";
    private static final int TAG_INTERNET_IOP = 0;
    private static final byte IIOP_MAJOR = 1;
    private static final byte IIOP_MINOR = 0;

    private static final byte ASCII_PERCENT = getASCIIByte("%");
    private static final byte ASCII_UPPER_A = getASCIIByte("A");
    private static final byte ASCII_UPPER_Z = getASCIIByte("Z");
    private static final byte ASCII_LOWER_A = getASCIIByte("a");
    private static final byte ASCII_LOWER_Z = getASCIIByte("z");
    private static final byte ASCII_ZERO = getASCIIByte("0");
    private static final byte ASCII_NINE = getASCIIByte("9");

    private static final byte ASCII_MINUS = getASCIIByte("-");
    private static final byte ASCII_UNDERSCORE = getASCIIByte("_");
    private static final byte ASCII_DOT = getASCIIByte(".");
    private static final byte ASCII_BANG = getASCIIByte("!");
    private static final byte ASCII_TILDE = getASCIIByte("~");
    private static final byte ASCII_STAR = getASCIIByte("*");
    private static final byte ASCII_QUOTE = getASCIIByte("'");
    private static final byte ASCII_OPEN_BRACKET = getASCIIByte("(");
    private static final byte ASCII_CLOSED_BRACKET = getASCIIByte("$");

    private static final char[] HEX = {
      '0','1','2','3','4','5','6','7',
      '8','9','a','b','c','d','e','f'
    };

    private static final byte getASCIIByte(String ch) {
      try {
	return (ch.getBytes("US-ASCII"))[0];
      }
      catch(UnsupportedEncodingException uee) {
	return 0;
      }
    }


    private final ORB orb;

    private String ior;
    private String host;
    private short port;
    private String objectKey;
    private String anchor;

    private CDRCodec codecStrategy;

    public IIOPAddress(ORB anOrb, FIPA.MTS objRef) throws MTPException {
      this(anOrb, anOrb.object_to_string(objRef));
    }

    public IIOPAddress(ORB anOrb, String s) throws MTPException {
      orb = anOrb;
      if(s.toLowerCase().startsWith("ior:"))
	initFromIOR(s);
      else if(s.toLowerCase().startsWith("corbaloc:"))
	initFromURL(s, BIG_ENDIAN);
      else if(s.toLowerCase().startsWith("corbaname:"))
	initFromNS(s);
      else
	throw new MTPException("Invalid string prefix");
    }

    void initFromIOR(String s) throws MTPException {
      parseIOR(s, FIPA_2000_TYPE_ID);
      anchor = "";
    }

    private void initFromURL(String s, short endianness) throws MTPException {

      // Remove 'corbaloc:' prefix to get URL host, port and file
      s = s.substring(9);

      if(s.toLowerCase().startsWith("iiop:")) {
	// Remove an explicit IIOP specification
	s = s.substring(5);
      }
      else if(s.startsWith(":")) {
	// Remove implicit IIOP specification
	s = s.substring(1);
      }
      else
	throw new MTPException("Invalid 'corbaloc' URL: neither 'iiop:' nor ':' was specified.");

      buildIOR(s, FIPA_2000_TYPE_ID, endianness);

    }

    private void initFromNS(String s) throws MTPException {
      // First perform a 'corbaloc::' resolution to get the IOR of the Naming Service.
      // Replace 'corbaname:' with 'corbaloc::'
      StringBuffer buf = new StringBuffer(s);

      // Use 'corbaloc' support to build a reference on the NamingContext
      // where the real object reference will be looked up.
      buf.replace(0, 11, "corbaloc::");
      buildIOR(s.substring(11), NS_TYPE_ID, BIG_ENDIAN);
      org.omg.CORBA.Object o = orb.string_to_object(ior);
      NamingContext ctx = NamingContextHelper.narrow(o);

      try {

	// Transform the string after the '#' sign into a COSNaming::Name.
	StringTokenizer lexer = new StringTokenizer(anchor, "/.", true);
	List name = new ArrayList();
	while(lexer.hasMoreTokens()) {
	  String tok = lexer.nextToken();
	  NameComponent nc = new NameComponent();
	  nc.id = tok;
	  name.add(nc);
	  if(!lexer.hasMoreTokens())
	    break; // Out of the while loop

	  tok = lexer.nextToken();
	  if(tok.equals(".")) { // An (id, kind) pair
	    tok = lexer.nextToken();
	    nc.kind = tok;
	  }
	  else if(!tok.equals("/")) // No separator other than '.' or '/' is allowed
	    throw new MTPException("Ill-formed path into the Naming Service: Unknown separator.");
	}

	// Get the object reference stored into the naming service...
	NameComponent[] path = (NameComponent[])name.toArray(new NameComponent[name.size()]);
	o = ctx.resolve(path);

	// Stringify it and use the resulting IOR to initialize yourself
      	String realIOR = orb.object_to_string(o);
	initFromIOR(realIOR);

      }
      catch(NoSuchElementException nsee) {
	throw new MTPException("Ill-formed path into the Naming Service.", nsee);
      }
      catch(UserException ue) {
	throw new MTPException("CORBA Naming Service user exception.", ue);
      }
      catch(SystemException se) {
	throw new MTPException("CORBA Naming Service system exception.", se);
      }
      

    }

    private void parseIOR(String s, String typeName) throws MTPException {
      try {
	// Store stringified IOR
	ior = s.toUpperCase();

	// Remove 'IOR:' prefix to get Hex digits
	String hexString = ior.substring(4);

	short endianness = Short.parseShort(hexString.substring(0, 2), 16);

	switch(endianness) {
	case BIG_ENDIAN:
	  codecStrategy = new BigEndianCodec(hexString);
	  break;
	case LITTLE_ENDIAN:
	  codecStrategy = new LittleEndianCodec(hexString);
	  break;
	default:
	  throw new MTPException("Invalid endianness specifier");
	}

	try {
	  // Read 'string type_id' field
	  String typeID = codecStrategy.readString();
	  if(!typeID.equalsIgnoreCase(typeName))
	    throw new MTPException("Invalid type ID" + typeID);
	}
	catch (Exception e) { // all exceptions are converted into MTPException
	  throw new MTPException("Invalid type ID");
	}

	// Read 'sequence<TaggedProfile> profiles' field
	// Read sequence length
	int seqLen = codecStrategy.readLong();
	for(int i = 0; i < seqLen; i++) {
	  // Read 'ProfileId tag' field
	  int tag = codecStrategy.readLong();
	  byte[] profile = codecStrategy.readOctetSequence();
	  if(tag == TAG_INTERNET_IOP) {
	    // Process IIOP profile
	    CDRCodec profileBodyCodec;
	    switch(profile[0]) {
	    case BIG_ENDIAN:
	      profileBodyCodec = new BigEndianCodec(profile);
	      break;
	    case LITTLE_ENDIAN:
	      profileBodyCodec = new LittleEndianCodec(profile);
	      break;
	    default:
	      throw new MTPException("Invalid endianness specifier");
	    }

	    // Read IIOP version
	    byte versionMajor = profileBodyCodec.readOctet();
	    byte versionMinor = profileBodyCodec.readOctet();
	    if(versionMajor != 1)
	      throw new MTPException("IIOP version not supported");

	    try {
	      // Read 'string host' field
	      host = profileBodyCodec.readString();
	    }
	    catch (Exception e) {
	      throw new MTPException("Invalid host string");
	    }

	    // Read 'unsigned short port' field
	    port = profileBodyCodec.readShort();

	    // Read 'sequence<octet> object_key' field and convert it
	    // into a String object
	    byte[] keyBuffer = profileBodyCodec.readOctetSequence();
	    ByteArrayOutputStream buf = new ByteArrayOutputStream();

	    // Escape every forbidden character, as for RFC 2396 (URI: Generic Syntax)
	    for(int ii = 0; ii < keyBuffer.length; ii++) {
	      byte b = keyBuffer[ii];
	      if(isUnreservedURIChar(b)) {
		// Write the character 'as is'
		buf.write(b);
	      }
	      else {
		// Escape it using '%'
		buf.write(ASCII_PERCENT);
		buf.write(HEX[(b & 0xF0) >> 4]); // High nibble
		buf.write(HEX[b & 0x0F]); // Low nibble
	      }
	    }

	    objectKey = buf.toString("US-ASCII");
	    codecStrategy = null;

	  }
	}
      }
      catch (Exception e) { // all exceptions are converted into MTPException
	throw new MTPException(e.getMessage());
      }
    }

    private void buildIOR(String s, String typeName, short endianness) throws MTPException {
      int colonPos = s.indexOf(':');
      int slashPos = s.indexOf('/');
      int poundPos = s.indexOf('#');
      if((colonPos == -1) || (slashPos == -1))
	throw new MTPException("Invalid URL string");

      host = s.substring(0, colonPos);
      port = Short.parseShort(s.substring(colonPos + 1, slashPos));
      if(poundPos == -1) {
	objectKey = s.substring(slashPos + 1, s.length());
	anchor = "";
      }
      else {
	objectKey = s.substring(slashPos + 1, poundPos);
	anchor = s.substring(poundPos + 1, s.length());
      }

      switch(endianness) {
      case BIG_ENDIAN:
	codecStrategy = new BigEndianCodec(new byte[0]);
	break;
      case LITTLE_ENDIAN:
	codecStrategy = new LittleEndianCodec(new byte[0]);
	break;
      default:
	throw new MTPException("Invalid endianness specifier");
      }

      codecStrategy.writeString(typeName);

      // Write '1' as profiles sequence length
      codecStrategy.writeLong(1);

      codecStrategy.writeLong(TAG_INTERNET_IOP);
      CDRCodec profileBodyCodec;
      switch(endianness) {
      case BIG_ENDIAN:
	profileBodyCodec = new BigEndianCodec(new byte[0]);
	break;
      case LITTLE_ENDIAN:
	profileBodyCodec = new LittleEndianCodec(new byte[0]);
	break;
      default:
	throw new MTPException("Invalid endianness specifier");
      }

      // Write IIOP 1.0 profile to auxiliary CDR codec
      profileBodyCodec.writeOctet(IIOP_MAJOR);
      profileBodyCodec.writeOctet(IIOP_MINOR);
      profileBodyCodec.writeString(host);
      profileBodyCodec.writeShort(port);
      try {
	byte[] objKey = objectKey.getBytes("US-ASCII");

	// Remove all the RFC 2396 escape sequences...
	ByteArrayOutputStream buf = new ByteArrayOutputStream();
	for(int i = 0; i < objKey.length; i++) {
	  byte b = objKey[i];
	  if(b != ASCII_PERCENT)
	    buf.write(b);
	  else {
	    // Get the hex value represented by the two bytes after '%'
	    try {
	      String hexPair = new String(objKey, i + 1, 2, "US-ASCII");
	      short sh = Short.parseShort(hexPair, 16);
	      if(sh > Byte.MAX_VALUE)
		b = (byte)(sh + 2*Byte.MIN_VALUE); // Conversion from unsigned to signed
	      else
		b = (byte)sh;
	    }
	    catch(UnsupportedEncodingException uee) {
	      b = 0;
	    }
	    buf.write(b);
	    i += 2;
	  }
	}

	profileBodyCodec.writeOctetSequence(buf.toByteArray());

	byte[] encapsulatedProfile = profileBodyCodec.writtenBytes();

	// Write encapsulated profile to main IOR codec
	codecStrategy.writeOctetSequence(encapsulatedProfile);

	String hexString = codecStrategy.writtenString();
	ior = "IOR:" + hexString;

	codecStrategy = null;

      }
      catch(UnsupportedEncodingException uee) {
	// It should never happen
	uee.printStackTrace();
      }

    }

    // This method returns true if and only if the supplied byte,
    // interpreted as an US-ASCII encoded character, corresponds to an
    // unreserved URI character. See RFC 2396 for details.
    private boolean isUnreservedURIChar(byte b) {
      // An upper case letter?
      if((ASCII_UPPER_A <= b)&&(ASCII_UPPER_Z >= b))
	return true;
      // A lower case letter?
      if((ASCII_LOWER_A <= b)&&(ASCII_LOWER_Z >= b))
	return true;
      // A decimal digit?
      if((ASCII_ZERO <= b)&&(ASCII_NINE >= b))
	return true;
      // An unreserved, but not alphanumeric character?
      if((b == ASCII_MINUS)||(b == ASCII_UNDERSCORE)||(b == ASCII_DOT)||(b == ASCII_BANG)||(b == ASCII_TILDE)||
	 (b == ASCII_STAR)||(b == ASCII_QUOTE)||(b == ASCII_OPEN_BRACKET)||(b == ASCII_CLOSED_BRACKET))
	return true;

      // Anything else is not allowed
      return false;
    }

    public String getURL() {
      int portNum = port;
      if(portNum < 0)
	portNum += 65536;
      return "corbaloc::" + host + ":" + portNum + "/" + objectKey;
    }

    public String getIOR() {
      return ior;
    }

    public FIPA.MTS getObject() {
      return FIPA.MTSHelper.narrow(orb.string_to_object(ior));
    }

    private static abstract class CDRCodec {

      protected byte[] readBuffer;
      protected StringBuffer writeBuffer;
      protected int readIndex = 0;
      protected int writeIndex = 0;

      protected CDRCodec(String hexString) {
	// Put all Hex digits into a byte array
	readBuffer = bytesFromHexString(hexString);
	readIndex = 1;
	writeBuffer = new StringBuffer(255);
      }

      protected CDRCodec(byte[] hexDigits) {
	readBuffer = new byte[hexDigits.length];
	System.arraycopy(hexDigits, 0, readBuffer, 0, readBuffer.length);
	readIndex = 1;
	writeBuffer = new StringBuffer(255);
      }

      public String writtenString() {
	return new String(writeBuffer);
      }

      public byte[] writtenBytes() {
	return bytesFromHexString(new String(writeBuffer));
      }

      public byte readOctet() {
	return readBuffer[readIndex++];
      }

      public byte[] readOctetSequence() {
	int seqLen = readLong();
	byte[] result = new byte[seqLen];
	System.arraycopy(readBuffer, readIndex, result, 0, seqLen);
	readIndex += seqLen;
	return result;
      }

      public String readString() { 
	  int strLen = readLong(); // This includes '\0' terminator
	  String result = new String(readBuffer, readIndex, strLen - 1);
	  readIndex += strLen;
	  return result;
      }

      // These depend on endianness, so are deferred to subclasses
      public abstract short readShort();   // 16 bits
      public abstract int readLong();      // 32 bits
      public abstract long readLongLong(); // 64 bits

      // Writes a couple of hexadecimal digits representing the given byte.
      // All other marshalling operations ultimately use this method to modify
      // the write buffer
      public void writeOctet(byte b) {
	char[] digits = new char[2];
	digits[0] = HEX[(b & 0xF0) >> 4]; // High nibble
	digits[1] = HEX[b & 0x0F]; // Low nibble
	writeBuffer.append(digits);
	writeIndex++;
      }

      public void writeOctetSequence(byte[] seq) {
	int seqLen = seq.length;
	writeLong(seqLen);
	for(int i = 0; i < seqLen; i++)
	  writeOctet(seq[i]);
      }

      public void writeString(String s) {
	int strLen = s.length() + 1; // This includes '\0' terminator
	writeLong(strLen);
	byte[] bytes = s.getBytes();
	for(int i = 0; i < s.length(); i++)
	  writeOctet(bytes[i]);
	writeOctet((byte)0x00);
      }

      // These depend on endianness, so are deferred to subclasses
      public abstract void writeShort(short s);   // 16 bits
      public abstract void writeLong(int i);      // 32 bits
      public abstract void writeLongLong(long l); // 64 bits

      protected void setReadAlignment(int align) {
	while((readIndex % align) != 0)
	  readIndex++;
      }

      protected void setWriteAlignment(int align) {
	while(writeIndex % align != 0)
	  writeOctet((byte)0x00);
      }

      private byte[] bytesFromHexString(String hexString) {
	int hexLen = hexString.length() / 2;
	byte[] result = new byte[hexLen];

	for(int i = 0; i < hexLen; i ++) {
	  String currentDigit = hexString.substring(2*i, 2*(i + 1));
	  Short s = Short.valueOf(currentDigit, 16);
	  result[i] = s.byteValue();
	}

	return result;
      }

    } // End of CDRCodec class

    private static class BigEndianCodec extends CDRCodec {

      public BigEndianCodec(String ior) {
	super(ior);
	writeOctet((byte)0x00); // Writes 'Big Endian' magic number
      }

      public BigEndianCodec(byte[] hexDigits) {
	super(hexDigits);
	writeOctet((byte)0x00); // Writes 'Big Endian' magic number
      }

      public short readShort() {
	setReadAlignment(2);
	short result = (short)((readBuffer[readIndex++] << 8) + readBuffer[readIndex++]);
	return result;
      }

      public int readLong() {
	setReadAlignment(4);
	int result = (readBuffer[readIndex++] << 24) + (readBuffer[readIndex++] << 16);
	result += (readBuffer[readIndex++] << 8) + readBuffer[readIndex++];
	return result;
      }

      public long readLongLong() {
	setReadAlignment(8);
	long result = (readBuffer[readIndex++] << 56) + (readBuffer[readIndex++] << 48);
	result += (readBuffer[readIndex++] << 40) + (readBuffer[readIndex++] << 32);
	result += (readBuffer[readIndex++] << 24) + (readBuffer[readIndex++] << 16);
	result += (readBuffer[readIndex++] << 8) + readBuffer[readIndex++];
	return result;
      }

      public void writeShort(short s) {
	setWriteAlignment(2);
	writeOctet((byte)((s & 0xFF00) >> 8));
	writeOctet((byte)(s & 0x00FF));
      }

      public void writeLong(int i) {
	setWriteAlignment(4);
	writeOctet((byte)((i & 0xFF000000) >> 24));
	writeOctet((byte)((i & 0x00FF0000) >> 16));
	writeOctet((byte)((i & 0x0000FF00) >> 8));
	writeOctet((byte)(i & 0x000000FF));
      }

      public void writeLongLong(long l) {
	setWriteAlignment(8);
	writeOctet((byte)((l & 0xFF00000000000000L) >> 56));
	writeOctet((byte)((l & 0x00FF000000000000L) >> 48));
	writeOctet((byte)((l & 0x0000FF0000000000L) >> 40));
	writeOctet((byte)((l & 0x000000FF00000000L) >> 32));
	writeOctet((byte)((l & 0x00000000FF000000L) >> 24));
	writeOctet((byte)((l & 0x0000000000FF0000L) >> 16));
	writeOctet((byte)((l & 0x000000000000FF00L) >> 8));
	writeOctet((byte)(l & 0x00000000000000FFL));
      }

    } // End of BigEndianCodec class

    private static class LittleEndianCodec extends CDRCodec {

      public LittleEndianCodec(String ior) {
	super(ior);
	writeOctet((byte)0x01); // Writes 'Little Endian' magic number
      }

      public LittleEndianCodec(byte[] hexDigits) {
	super(hexDigits);
	writeOctet((byte)0x01); // Writes 'Little Endian' magic number
      }

      public short readShort() {
	setReadAlignment(2);
	short result = (short)(readBuffer[readIndex++] + (readBuffer[readIndex++] << 8));
	return result;
      }

      public int readLong() {
	setReadAlignment(4);
	int result = readBuffer[readIndex++] + (readBuffer[readIndex++] << 8) + (readBuffer[readIndex++] << 16) + (readBuffer[readIndex++] << 24);
	return result;
      }

      public long readLongLong() {
	setReadAlignment(8);
	long result = readBuffer[readIndex++] + (readBuffer[readIndex++] << 8);
	result += (readBuffer[readIndex++] << 16) + (readBuffer[readIndex++] << 24);
	result += (readBuffer[readIndex++] << 32) + (readBuffer[readIndex++] << 40);
	result += (readBuffer[readIndex++] << 48) + (readBuffer[readIndex++] << 56);
	return result;
      }

      public void writeShort(short s) {
	setWriteAlignment(2);
	writeOctet((byte)(s & 0x00FF));
	writeOctet((byte)((s & 0xFF00) >> 8));
      }

      public void writeLong(int i) {
	setWriteAlignment(4);
	writeOctet((byte)(i & 0x000000FF));
	writeOctet((byte)((i & 0x0000FF00) >> 8));
	writeOctet((byte)((i & 0x00FF0000) >> 16));
	writeOctet((byte)((i & 0xFF000000) >> 24));
      }

      public void writeLongLong(long l) {
	setWriteAlignment(8);
	writeOctet((byte)(l & 0x00000000000000FFL));
	writeOctet((byte)((l & 0x000000000000FF00L) >> 8));
	writeOctet((byte)((l & 0x0000000000FF0000L) >> 16));
	writeOctet((byte)((l & 0x00000000FF000000L) >> 24));
	writeOctet((byte)((l & 0x000000FF00000000L) >> 32));
	writeOctet((byte)((l & 0x0000FF0000000000L) >> 40));
	writeOctet((byte)((l & 0x00FF000000000000L) >> 48));
	writeOctet((byte)((l & 0xFF00000000000000L) >> 56));
      }

    }  // End of LittleEndianCodec class

    public String getProto() {
      return "iiop";
    }

    public String getHost() {
      return host;
    }

    public String getPort() {
      return Short.toString(port);
    }

    public String getFile() {
      return objectKey;
    }

    public String getAnchor() {
      return anchor;
    }
    
    

  } // End of IIOPAddress class

