Skip to main content.

Web Based Programming Tutorials

Homepage | Forum - Join the forum to discuss anything related to programming! | Programming Resources

Java 1.2 Unleashed

Java 1.2 Unleashed -- Ch 41 -- Java IDL and ORBs


Java 1.2 Unleashed

Previous chapterNext chapterContents


- 14 -

Java IDL and ORBs




In Chapter 37, "Distributed Application Architecture," you learned about different approaches to developing distributed applications. One of these approaches was the Common Object Request Broker Architecture (CORBA). You learned about the similarities and differences between Java RMI and CORBA, and how RMI provides a pure Java implementation of the distributed object model used by CORBA. Although RMI is much easier to use to develop distributed applications in Java, it is limited to pure Java applications. CORBA, on the other hand, is language-neutral and allows you to interface Java objects with objects written in other programming languages.

In this chapter, you'll learn how to create CORBA objects using Java and how to connect Java objects to CORBA objects. You'll learn how Object Request Brokers (ORBs) are used to connect distributed objects, how the Interface Definition Language (IDL) is used to define object interfaces, and how the idltojava compiler is used to create Java stub and skeleton classes. You'll also learn about Java IDL and the tnameserv naming service tool. When you finish this chapter, you'll know how to integrate Java with CORBA.

The Object Management Group and CORBA

The Object Management Group (OMG), which can be reached at http://www.omg.org, is a consortium of companies and other organizations that was founded in 1989 to promote the development of component-based software through the establishment of object-oriented software standards and guidelines. The OMG has hundreds of member organizations.

Since its creation, the OMG has focused on standards for implementing distributed objects. CORBA is a result of this effort. It provides a standard architecture for developing distributed object-oriented systems. This architecture specifies how a client object written in one language can invoke the methods of a remote server object developed in a different language.




NOTE: An overview of CORBA is provided in Chapter 37, "Distributed Application Architecture."

CORBA uses stubs and skeletons in much the same way as Java RMI. A stub is a local proxy for a remote object. It presents the same interface as the server object, but runs on the same computer as the client. A skeleton is a remote interface to the server object's implementation. It runs on the same computer as the server object and provides an interface between the server object's implementation and other objects.

The stub and skeleton are connected via an ORB. The ORB forwards method invocations from the stub to the skeleton and uses a special object called an Object Adapter (OA), which runs on the same computers as the server object. The OA activates the server object, if required, and helps to manage its operation. You can think of the ORB as analogous to the remote reference and transport layers of Java RMI, and the OA as being like the remote registry. The OA is sometimes referred to as a Basic Object Adapter (BOA).

Figure 41.1 provides an overview of how client and server objects communicate using CORBA. When a client object invokes the methods of a server object, it does so via the local stub of the server object. The stub presents two interfaces: its interface to the client object is in a form that is recognized by the client object's implementation, and its interface to the ORB is in a form that is common to the ORB.

FIGURE 41.1. How client and server objects communicate using CORBA.

The local stub communicates with an ORB and provides it with the name of the object, the method being invoked, and any arguments to the method invocation. The ORB finds the referenced object in its network domain (often using a CORBA name service) and contacts the OA running on the same computer as the server object. The ORB passes the method invocation and argument information to the OA.

The OA will do a number of things based on the way that it manages objects on the server's computer. Its basic responsibility, however, is to activate the server object's implementation and then pass the method invocation and arguments to the server object's implementation via the skeleton. The server object processes the method invocation and returns any results to the skeleton, which forwards them to the ORB.

The skeleton, like the stub, presents two interfaces. Its interface to the OA and ORB is a standard interface that each expects. Its interface with the server object is in a form that is known to the server object. Later in this chapter, you'll learn how stubs and skeletons are specified in the Interface Definition Language and automatically generated.

When the skeleton returns a value to the ORB, the ORB forwards the return value to the stub and the stub returns the value to the client object.

ORBs

The ORB is the key to making CORBA work, the glue that holds together stubs, skeletons, naming services, and OAs. It is responsible for communicating with the stub, finding the server object, and communicating with the OA and skeleton. It provides a language-neutral interface to the stub and skeleton, and helps the stub and skeleton to abstract away the fact that the client and server objects are implemented on different computers and in different languages. To the client, there must be no difference between invoking the methods of a local object and a remote object. To the server, the fact that its methods are being invoked remotely is transparent. The transparency that the ORB, stub, and skeleton provide to the client and server objects is the greatest feature of CORBA.

In Figure 41.1, the ORB is shown as a single entity between the client and server. This is a logical representation of the ORB. The actual implementation may vary depending upon the ORB. It may be implemented as peer components that are installed on both the client and server's computers, or as an ORB server that resides on a separate computer. Other ORB implementations are also possible.


NOTE: The part of the ORB that provides the basic representation of objects and communication of requests is referred to as the ORB core.

Interoperability Between ORBs

There may be multiple ORB implementations from different vendors within the same network that are used in the same distributed application. A client may simultaneously use two different ORBs to access multiple objects, or one ORB to access an object that is serviced by another ORB. Because multiple ORBs may exist within the same application, it is necessary that the ORBs be able to communicate with each other.

The General Inter-ORB Protocol (GIOP) is the common interface that is used to support communication between ORBs. The GIOP specifies a syntax and a set of message formats for inter-ORB communication. Because ORBs may be implemented on networks that use a variety of transport protocols, such as TCP/IP, IPX, or SNA, the protocol used to transport information between ORBs is not specified in GIOP.

The Internet Inter-ORB Protocol (IIOP) is used to map the GIOP to the TCP/IP protocol suite. Different ORBs can communicate with each other across TCP/IP networks using GIOP and IIOP, as shown in Figure 41.2.

FIGURE 41.2. Different ORBs communicate using GIOP and IIOP across TCP/IP networks.

In addition to GIOP and IIOP, CORBA also provides Environment-Specific Inter-ORB Protocols (ESIOPs), which are used to connect legacy applications to distributed applications that are CORBA-compliant. Legacy systems use ESIOPs to communicate with ORBs. ESIOPs, like the GIOP, need to be tailored to their networking environment. The DCE Common Inter-ORB Protocol (DCE-CIOP) is used with ESIOP to integrate DCE applications with CORBA applications.

The Interface Definition Language

Now that you have the big picture on CORBA, we'll cover one more important element of it before we look at Java IDL. Because one of CORBA's main goals is to provide a distributed object-oriented framework in which objects created in a variety of programming languages can interact, it needs a way to bridge the gap between multiple programming language interfaces and the ORB. IDL is the key to achieving this goal.

IDL provides a language-neutral way of describing the interfaces of objects. It describes an interface in the same manner that Java interfaces do. It defines the methods contained in an interface, their arguments, and their return values, but does not specify how the interfaces are implemented.

A server object's interface is specified in IDL, and the IDL specification is compiled to produce the stub and skeleton to be used for that object. For example, you could specify the interface of a server in IDL and then compile the IDL to create the C++ source code for the server's skeleton. You could also compile the same IDL to create a Java stub.

IDL compilers are available for C, C++, Smalltalk, Ada, and (of course) Java. These compilers translate IDL into stubs and skeletons in the source code of these languages. You then use a language-specific compiler to compile the stubs and skeletons to binary code or byte code.

In order to develop an IDL compiler for a particular language, a language mapping must be developed that shows how the datatypes and method invocation semantics of IDL map to the language. The Java language mapping has been completed and is available at http://splash.javasoft.com/products/jdk/idl/docs/idl-java.html. An IDL-to-Java compiler, aptly named idltojava, is also available from the Java Developer Connection at http://developer.javasoft.com. You should download idltojava to perform the example in this chapter. Copy the idltojava program to a directory that is in your execution path.

Using Java IDL

The capability to use Java objects within CORBA is referred to as Java IDL and has been incorporated into JDK 1.2. Java IDL is based upon the following:

You don't need to understand the IDL-to-Java mapping to use Java with CORBA. However, downloading and reading the mapping will help you to learn IDL, which you'll need to do if you want to use CORBA. Luckily, it is an easy language to learn. The CORBA/IIOP 2.1 specification can be downloaded from http://www.omg.org. Section 3 provides an introduction to IDL.

Listing 41.1 provides a small sample IDL description. It defines the MyServer interface that is contained in the sample module. An IDL interface corresponds to a Java interface. An IDL module corresponds to a Java package. The getServerData() method takes the string input argument identified by the dataID variable and returns an IDL string value. (In our example, the getServerData() method will simply convert its string argument to uppercase.)

You can create Java stubs and skeletons for this interface using the following command:

idltojava MyServer.idl

This generates the following five Java files in the example subdirectory of your ch41 directory:

Note how the example IDL module was translated into the example Java package, and the MyServer IDL interface was translated into the MyServer Java interface. Also note how the getServerData() method was created. You can look through the rest of the Java files that were generated. These files are used to implement the skeleton and stub of the MyServer interface.


NOTE: The example files are created in the example subdirectory of your ch41 directory.

LISTING 41.1. AN IDL DESCRIPTION OF THE MyServer INTERFACE.

module example {

 interface MyServer {

  string getServerData(in string dataID);

 };

};


NOTE: The idltojava tool requires a C/C++ preprocessor to be installed on your system. If you have a C or C++ compiler on your system, idltojava should find it. You can suppress the use of the preprocessor by using the -fno-cpp option with idltojava. The preprocessor supports the IDL-to-Java translation process. If you are using Visual C++, you should adjust your PATH to include the path to CL.EXE. You can find CL.EXE using the Windows 95, 98, or NT Find command, which is accessible from the Start button.

LISTING 41.2. THE MyServer.java FILE GENERATED BY idltojava.

/*

 * File: ./EXAMPLE/MYSERVER.JAVA

 * From: MYSERVER.IDL

 * Date: Mon Feb 23 00:28:16 1998

 *   By: C:\JDK1~1.2BE\BIN\IDLTOJ~1.EXE Java IDL 1.2 Nov 10 1997 13:52:11

 */

package example;

public interface MyServer

    extends org.omg.CORBA.Object {

    String getServerData(String dataID)

;

}

LISTING 41.3. THE _MyServerImplBase.java FILE GENERATED BY idltojava.

/*

 * File: ./EXAMPLE/_MYSERVERIMPLBASE.JAVA

 * From: MYSERVER.IDL

 * Date: Mon Feb 23 00:28:16 1998

 *   By: C:\JDK1~1.2BE\BIN\IDLTOJ~1.EXE Java IDL 1.2 Nov 10 1997 13:52:11

 */

package example;

public abstract class _MyServerImplBase extends org.omg.CORBA.DynamicImplementation implements example.MyServer {

    // Constructor

    public _MyServerImplBase() {

         super();

    }

    // Type strings for this class and its superclasses

    private static final String _type_ids[] = {

        "IDL:example/MyServer:1.0"

    };

    public String[] _ids() { return (String[]) _type_ids.clone(); }

    private static java.util.Dictionary _methods = new     Âjava.util.Hashtable();

    static {

      _methods.put("getServerData", new java.lang.Integer(0));

     }

    // DSI Dispatch call

    public void invoke(org.omg.CORBA.ServerRequest r) {

       switch (((java.lang.Integer) _methods.get(r.op_name())).intValue()){

           case 0: // example.MyServer.getServerData

              {

              org.omg.CORBA.NVList _list = _orb().create_list(0);

              org.omg.CORBA.Any _dataID = _orb().create_any();

              _dataID.type(org.omg.CORBA.ORB.init().get_primitive_tc               Â(org.omg.CORBA.TCKind.tk_string));

              _list.add_value("dataID", _dataID, org.omg.CORBA.ARG_IN.               Âvalue);

              r.params(_list);

              String dataID;

              dataID = _dataID.extract_string();

              String ___result;

                            ___result = this.getServerData(dataID);

              org.omg.CORBA.Any __result = _orb().create_any();

              __result.insert_string(___result);

              r.result(__result);

              }

              break;

            default:

              throw new org.omg.CORBA.BAD_OPERATION(0,               Âorg.omg.CORBA.CompletionStatus.COMPLETED_MAYBE);

       }

 }

}

LISTING 41.4. THE _MyServerStub.java FILE GENERATED BY idltojava.

/*

 * File: ./EXAMPLE/_MYSERVERSTUB.JAVA

 * From: MYSERVER.IDL

 * Date: Mon Feb 23 00:28:16 1998

 *   By: C:\JDK1~1.2BE\BIN\IDLTOJ~1.EXE Java IDL 1.2 Nov 10 1997 13:52:11

 */

package example;

public class _MyServerStub

    extends org.omg.CORBA.portable.ObjectImpl

        implements example.MyServer {

    public _MyServerStub(org.omg.CORBA.portable.Delegate d) {

          super();

          _set_delegate(d);

    }

    private static final String _type_ids[] = {

        "IDL:example/MyServer:1.0"

    };

    public String[] _ids() { return (String[]) _type_ids.clone(); }

    //    IDL operations

    //        Implementation of ::example::MyServer::getServerData

    public String getServerData(String dataID)

 {

           org.omg.CORBA.Request r = _request("getServerData");

           r.set_return_type(org.omg.CORBA.ORB.init().get_primitive_tc            Â(org.omg.CORBA.TCKind.tk_string));

           org.omg.CORBA.Any _dataID = r.add_in_arg();

           _dataID.insert_string(dataID);

           r.invoke();

           String __result;

           __result = r.return_value().extract_string();

           return __result;

   }

};

LISTING 41.5. THE MyServerHelper.java FILE GENERATED BY idltojava.

/*

 * File: ./EXAMPLE/MYSERVERHELPER.JAVA

 * From: MYSERVER.IDL

 * Date: Mon Feb 23 00:28:16 1998

 *   By: C:\JDK1~1.2BE\BIN\IDLTOJ~1.EXE Java IDL 1.2 Nov 10 1997 13:52:11

 */

package example;

public class MyServerHelper {

     // It is useless to have instances of this class

     private MyServerHelper() { }

    public static void write(org.omg.CORBA.portable.OutputStream out,     Âexample.MyServer that) {

        out.write_Object(that);

    }

    public static example.MyServer read(org.omg.CORBA.portable.InputStream     Âin) {

        return example.MyServerHelper.narrow(in.read_Object());

    }

   public static example.MyServer extract(org.omg.CORBA.Any a) {

     org.omg.CORBA.portable.InputStream in = a.create_input_stream();

     return read(in);

   }

   public static void insert(org.omg.CORBA.Any a, example.MyServer that) {

     org.omg.CORBA.portable.OutputStream out = a.create_output_stream();

     write(out, that);

     a.read_value(out.create_input_stream(), type());

   }

   private static org.omg.CORBA.TypeCode _tc;

   synchronized public static org.omg.CORBA.TypeCode type() {

          if (_tc == null)

             _tc = org.omg.CORBA.ORB.init().create_interface_tc(id(),              Â"MyServer");

      return _tc;

   }

   public static String id() {

       return "IDL:example/MyServer:1.0";

   }

   public static example.MyServer narrow(org.omg.CORBA.Object that)

        throws org.omg.CORBA.BAD_PARAM {

        if (that == null)

            return null;

        if (that instanceof example.MyServer)

            return (example.MyServer) that;

    if (!that._is_a(id())) {

        throw new org.omg.CORBA.BAD_PARAM();

    }

        org.omg.CORBA.portable.Delegate dup =         Â((org.omg.CORBA.portable.ObjectImpl)that)._get_delegate();

        example.MyServer result = new example._MyServerStub(dup);

        return result;

   }

}

LISTING 41.6. THE MyServerHolder.java FILE GENERATED BY idltojava.

/*

 * File: ./EXAMPLE/MYSERVERHOLDER.JAVA

 * From: MYSERVER.IDL

 * Date: Mon Feb 23 00:28:16 1998

 *   By: C:\JDK1~1.2BE\BIN\IDLTOJ~1.EXE Java IDL 1.2 Nov 10 1997 13:52:11

 */

package example;

public final class MyServerHolder

     implements org.omg.CORBA.portable.Streamable{

    //    instance variable 

    public example.MyServer value;

    //    constructors 

    public MyServerHolder() {

    this(null);

    }

    public MyServerHolder(example.MyServer __arg) {

    value = __arg;

    }

    public void _write(org.omg.CORBA.portable.OutputStream out) {

        example.MyServerHelper.write(out, value);

    }

    public void _read(org.omg.CORBA.portable.InputStream in) {

        value = example.MyServerHelper.read(in);

    }

    public org.omg.CORBA.TypeCode _type() {

        return example.MyServerHelper.type();

    }

}

Developing Clients and Servers

Now that you've used idltojava to generate stub and skeleton files, you need to develop a client that remotely accesses the server and provide an implementation of the server interface.

Creating the Client

The client will simply pass the strings "java", "corba", "orb", and "idl" to the remote server and display the values returned by the server. Most of its processing will involve setting up the connection to the remote object, invoking the remote object, and processing the results of the method invocations. Listing 41.7 shows the source code for the MyClient program. (I could have named this program anything. There is no naming relationship between MyClient and MyServer. I point this out because there are conventions that should be followed when naming the server source code files.)

LISTING 41.7. THE MyClient PROGRAM.

import org.omg.CORBA.*;      // Access the Java ORB

import org.omg.CosNaming.*;  // Acccess the CORBA name service

import example.*;  // Access the MyServer interface and stub

public class MyClient {

 public static void main(String[] args) {

  try {

   // Obtain a reference to the Java ORB and initialize it

   ORB orb = ORB.init(args,null);

   // Obtain a reference to the name service

   org.omg.CORBA.Object objRef = 

    orb.resolve_initial_references("NameService");

   // Cast the reference to a NamingContext object

   NamingContext ncRef = NamingContextHelper.narrow(objRef);

   // Define the first (and only) component of MyServer's name

   NameComponent nc = new NameComponent("MyServer", "");

   // Create a path name for MyServer

   NameComponent path[] = {nc};

   // Use the naming service to obtain a reference to MyServer

   // and cast the reference to a MyServer object

   MyServer serverRef = MyServerHelper.narrow(ncRef.resolve(path));

   String[] data = {"java","corba","orb","idl"};

   for(int i=0;i<data.length;++i) {

    System.out.println("Sent: "+data[i]);

    // Invoke the getServerData() method of MyServer and

    // display the result returned

    System.out.println("Received: "+serverRef.getServerData(data[i]));

   }

  }catch(Exception ex){

   System.out.println(ex.toString());

}

 }

};

The MyClient program is a simple console program that exercises the MyServer CORBA object. It illustrates the basics of developing a CORBA client in Java. The first items of interest are the import statements. The org.omg.CORBA package is imported by all Java CORBA clients and servers because it provides access to the ORB class. The ORB class is the Java implementation of a CORBA object request broker. The org.omg.CosNaming package is imported to access the CORBA naming service. The naming service is used to obtain a local reference to a named CORBA object--that is, an instance of MyServer. The examples package is the Java package corresponding to the MyServer IDL module. This package contains the classes and interfaces defined in Listings 41.2 through 41.6.

The processing within the main() method is wrapped in a try-catch statement to catch any exceptions that may occur in using the CORBA interface. The first thing that we do within the try statement is to obtain a reference to the ORB and then initialize it. The args command-line arguments are passed on to the ORB initialization. This simplifies the setup of the ORB on a particular port. You'll see how this works when we run MyClient.

The next thing we do in setting up the CORBA interface is to obtain a reference to the name service. The name service is used to locate a remote object by its name. The reference is returned as an object of class org.omg.CORBA.Object. This class is at the top of the CORBA class hierarchy and is a subclass of java.lang.Object. The narrow() method of the NamingContextHelper class is used to convert the reference from an object of the org.omg.CORBA.Object class to an object of the NamingContext class. The NamingContext class provides access to the naming service.

CORBA supports names that are made up of multiple name components. The MyServer object consists of only a single MyServer name component. A NameComponent object is created for MyServer. This component is converted into a path consisting of this single component. The path is implemented as a NameComponent array.

A reference to an instance of MyServer is obtained by invoking the resolve() method of the NamingContext object (that is, the name server). The reference returned by resolve() is cast into an object of the MyServer interface by invoking the static narrow() method of the MyServerHelper class. The MyServerHelper class was automatically generated by idltojava. This class provides methods for converting CORBA objects to and from the MyServer interface.

We now have a local reference to an instance of MyServer. The setup portion of MyClient is complete. The rest of the program uses the MyServer reference to carry out the program's processing. An array of strings is created to test the getServerData() method of MyServer. A for statement is used to invoke getServerData() with these strings and display the results that getServerData()returns.

Creating the Server

Java IDL supports a two-tiered server implementation. The first tier consists of a servant class that provides the actual implementation of the remote object interface. The second tier consists of a server class that creates servant instances. The servant class is named by appending Servant to the name of the object's IDL interface. The server class is named by appending Server to the interface name. Our interface is MyServer, so MyServerServant and MyServerServer are the names of the servant and server classes.

Listing 41.8 shows the source code of the MyServerServant class. This class extends the skeleton _MyServerImplBase and implements the getServerData() method. This method simply converts strings to uppercase.

LISTING 41.8. THE MyServerServant CLASS.

import org.omg.CORBA.*;

import org.omg.CosNaming.*;

import org.omg.CosNaming.NamingContextPackage.*;

import example.*;

public class MyServerServant extends _MyServerImplBase {

 public String getServerData(String in) {

  return in.toUpperCase();

 }

}

The MyServerServer class is shown in Listing 41.9. This class is responsible for instantiating the servant and registering it with the ORB and name service. You'll notice that the CORBA setup processing is very similar to that performed by MyClient. It begins by initializing the ORB and then creates an instance of MyServerServant. It then connects the servant to the ORB.

Next, the server registers the servant with the name service. It does this by obtaining a reference to the name service, creating a path name for MyServer, and binding the servant instance to the path name in the name service.

Finally, an object is created that puts the server thread in a wait state so that method invocations from the client (MyClient) can be received.

LISTING 41.9. THE MyServerServer CLASS.

import org.omg.CORBA.*;

import org.omg.CosNaming.*;

import org.omg.CosNaming.NamingContextPackage.*;

import example.*;

public class MyServerServer {

 public static void main(String[] args){

  try{

   // Access and initialize the ORB

   ORB orb = ORB.init(args, null);

   // Create a servant instance

   MyServerServant myServerRef = new MyServerServant();

   // Connect the servant to the ORB

   orb.connect(myServerRef);

   // Obtain a reference to the name service

   org.omg.CORBA.Object objRef =

    orb.resolve_initial_references("NameService");

   // Cast the name service reference as a NamingContext object

   NamingContext ncRef = NamingContextHelper.narrow(objRef);

   // Create the name component for MyServer

   NameComponent nc = new NameComponent("MyServer", "");

   // Create the path name for MyServer

   NameComponent path[] = {nc};

   // Bind the path name with the servant in the name service

   // This registers the servant with the MyServer name

   ncRef.rebind(path, myServerRef);

   // Wait for and respond to remote method invocations

   java.lang.Object sync = new java.lang.Object();

   synchronized(sync){

    sync.wait(); 

   }

  }catch(Exception ex){

   System.out.println(ex.toString());

  }

 }

}

Compiling and Running the Client and Server

You've covered all the code needed to create client and server objects in Java that communicate using the Java ORB and naming service provided with the JDK. All you need to do is to compile the server and client and run them with the name service:

1. Change directories to the ch41\example directory and compile the stub, skeleton, interface, and support files by entering javac *.java.

2. Change directories to the ch41 directory and compile MyServerServant.java.

3. Compile MyServerServer.java.

4. Compile MyClient.java.

5. Start the name service by entering tnameserv.

6. Start the MyServerServer program by entering java MyServerServer in a separate console window.

7. Run MyClient in another console window (or on another host) using java MyClient.


NOTE: To run tnameserv and MyServerServer on another host, pass the -ORBInitialHost argument to MyClient. For example, if the servers run on server.com, you would invoke MyClient with java MyClient -ORBInitialHost server.com.

The MyClient program displays the following results:

Sent: java

Received: JAVA

Sent: corba

Received: CORBA

Sent: orb

Received: ORB

Sent: idl

Received: IDL

The name service, MyServerServer, and MyClient programs all communicate on TCP port 900 as a default. Use of this port requires root access on UNIX machines and Administrator privilege under Windows NT. If you'd like to use a different TCP port, pass the -ORBInitialPort option to each of the programs, as follows:

tnameserv -ORBInitialPort port

java MyServerServer -ORBInitialPort port

java MyClient -ORBInitialPort port

Substitute the desired TCP port number for port.

Summary

In this chapter, you learned the basics of how CORBA works and how Java objects can be integrated with CORBA. You learned how ORBs are used to connect distributed objects, how IDL is used to define object interfaces, and how Java IDL supports Java-CORBA integration. You also developed client and server objects in Java that communicated with each other using the Java ORB and naming service. In the next chapter, you'll learn about network computers and how Sun's JavaStation provides a pure Java solution to network computing.


Previous chapterNext chapterContents

© Copyright, Macmillan Computer Publishing. All rights reserved.