Return-Path: <Mailing@hermes.java.sun.com>
Received: from MIT.EDU by po10.mit.edu (8.9.2/4.7) id VAA11442; Tue, 6 Mar 2001 21:50:06 -0500 (EST)
Received: from hermes.java.sun.com by MIT.EDU with SMTP
	id AA00243; Tue, 6 Mar 01 21:48:41 EST
Date: Tue, 6 Mar 01 21:48:41 EST
Message-Id: <10103070248.AA00243@MIT.EDU>
From: "JDC Tech Tips"<Mailing@hermes.java.sun.com>
To: alexp@MIT.EDU
Subject: JDC Tech Tips  March 6, 2001
Reply-To: JDCTechTips@hermes.java.sun.com
Errors-To: bounced_mail@hermes.java.sun.com
Precedence: junk
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
X-Mailer: Beyond Email 2.2


 J  D  C    T  E  C  H    T  I  P  S

                      TIPS, TECHNIQUES, AND SAMPLE CODE


WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, 
March 6, 2001. This issue covers:

         * Cloning Objects
         * Using the Serializable Fields API 
         
These tips were developed using Java(tm) 2 SDK, Standard Edition, 
v 1.3.

You can view this issue of the Tech Tips on the Web at
http://java.sun.com/jdc/JDCTechTips/2001/tt0306.html

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
CLONING OBJECTS

Suppose you have some objects that you're using in an 
application. How can you make copies of the objects? The most 
obvious approach is to simply assign one object to another, like 
this:

    obj2 = obj1;

But this approach actually does no copying of the objects; 
instead the approach only copies the object references. In other 
words, after you perform this operation, there is still only one 
object, but now there is an additional reference to the object.

If this seemingly obvious approach doesn't work, how do you 
actually clone an object? Why not try the method Object.clone? 
This method is available to all of Object's subclasses. Here's an 
attempt:

    class A {
        private int x;
        public A(int i) {
            x = i;
        }
    }
    
    public class CloneDemo1 {
        public static void main(String args[])
          throws CloneNotSupportedException {
            A obj1 = new A(37);
            A obj2 = (A)obj1.clone();
        }
    }

This code triggers a compile error, because Object.clone is a
protected method.

So let's try again, with another approach:

    class A {
        private int x;
        public A(int i) {
            x = i;
        }
        public Object clone() {
            try {
                return super.clone();
            }
            catch (CloneNotSupportedException e) {
                throw new InternalError(e.toString());
            }
        }
    }
    
    public class CloneDemo2 {
        public static void main(String args[])
          throws CloneNotSupportedException {
            A obj1 = new A(37);
            A obj2 = (A)obj1.clone();
        }
    }
    
In this approach, you define your own clone method, which extends
Object.clone. The CloneDemo2 program compiles, but gives a
CloneNotSupportedException when you try to run it. 

There's still a piece missing. You have to specify that the class 
containing the clone method implements the Cloneable interface, 
like this:

    class A implements Cloneable {
        private int x;
        public A(int i) {
            x = i;
        }
        public Object clone() {
            try {
                return super.clone();
            }
            catch (CloneNotSupportedException e) {
                throw new InternalError(e.toString());
            }
        }
        public int getx() {
            return x;
        }
    }
    
    public class CloneDemo3 {
        public static void main(String args[])
          throws CloneNotSupportedException {
            A obj1 = new A(37);
            A obj2 = (A)obj1.clone();
            System.out.println(obj2.getx());
        }
    }
    
Success! CloneDemo3 compiles and produces the expected result:

37    

You've learned that you must explicitly specify the clone method, 
and your class must implement the Cloneable interface. Cloneable 
is an example of a "marker" interface. The interface itself 
specifies nothing. However, Object.clone checks whether a class 
implements it, and if not, throws a CloneNotSupportedException.

Object.clone does a simple cloning operation, copying all 
fields from one object to a new object. In the CloneDemo3 
example, A.clone calls Object.clone. Then Object.clone creates 
a new A object and copies the fields of the existing object to 
it.

There are a couple of other important points to consider about 
the approach illustrated in the CloneDemo3 example. One is that 
you can prevent a user of your class from cloning objects of 
the class. To do this, you don't implement Cloneable for the 
class, and the clone method always throws an exception. Most of 
the time, however, it's better to explicitly plan for and 
implement a clone method in your class so that objects are 
copied appropriately. 

Another point is that you can support cloning either
unconditionally or conditionally. The code in the CloneDemo3 
example supports cloning unconditionally, and the clone method 
does not propagate CloneNotSupportedException.

A more general approach is to conditionally support cloning for 
a class. In this case, objects of the class itself can be cloned, 
but objects of subclasses possibly cannot be cloned. For 
conditional cloning, the clone method must declare that it can 
propagate CloneNotSupportedException. Another example of 
conditional support for cloning is where a class is a collection 
class whose objects can be cloned only if the collection elements 
can be cloned.

Yet another approach is to implement an appropriate clone method 
in a class, but not implement Cloneable. In that case, subclasses 
can support cloning if they wish.

Cloning can get tricky. For example, because Object.clone does 
a simple object field copy, it sometimes might not be what you 
want. Here's an example:

    import java.util.*;
    
    class A implements Cloneable {
        public HashMap map;
        public A() {
            map = new HashMap();
            map.put("key1", "value1");
            map.put("key2", "value2");
        }
        public Object clone() {
            try {
                return super.clone();
            }
            catch (CloneNotSupportedException e) {
                throw new InternalError(e.toString());
            }
        }
    }
    
    public class CloneDemo4 {
        public static void main(String args[]) {
            A obj1 = new A();
            A obj2 = (A)obj1.clone();
    
            obj1.map.remove("key1");
    
            System.out.println(obj2.map.get("key1"));
        }
    }
    
You might expect CloneDemo4 to display the result:

value1

But instead it displays:

null

What's happening here? In CloneDemo4, A objects contain a HashMap 
reference. When A objects are copied, the HashMap reference is 
also copied. This means that an object clone contains the 
original reference to the HashMap object. So when a key is 
removed from the HashMap in the original object, the HashMap in 
the copy is updated as well. 

To fix this problem, you can make the clone method a bit more
sophisticated:

    import java.util.*;
    
    class A implements Cloneable {
        public HashMap map;
        public A() {
            map = new HashMap();
            map.put("key1", "value1");
            map.put("key2", "value2");
        }
        public Object clone() {
            try {
                A aobj = (A)super.clone();
                aobj.map = (HashMap)map.clone();
                return aobj;
            }
            catch (CloneNotSupportedException e) {
                throw new InternalError(e.toString());
            }
        }
    }
    
    public class CloneDemo5 {
        public static void main(String args[]) {
            A obj1 = new A();
            A obj2 = (A)obj1.clone();
    
            obj1.map.remove("key1");
    
            System.out.println(obj2.map.get("key1"));
        }
    }
    
The Clone5Demo example displays the expected result:

value1    

Clone5Demo calls super.clone to create the A object and copy the
map field. The example code then calls HashMap.clone to do a 
special type of cloning peculiar to HashMaps. This operation 
consists of creating a new hash table and copying entries to it 
from the old one.

If two objects share a reference, as in CloneDemo4, then you're 
likely to have problems unless the reference is read-only. To get 
around the problems, you need to implement a clone method that 
handles this situation. Another way of saying it is that 
Object.clone does a "shallow" copy, that is, a simple 
field-by-field copy. It doesn't do a "deep" copy, where each
object referred to by a field or array is itself recursively 
copied.

It's extremely important to call super.clone, instead of, for 
example, saying "new CloneDemo5" to create an object. You should
call super.clone at each level of the class hierarchy. That's 
because each level might have its own problems with shared 
objects. If you use "new" instead of super.clone, then your code 
will be incorrect for any subclass that extends your class; the 
code will call your clone method and receive an incorrect object 
type in return.

One other thing to know about cloning is that it's possible to 
clone any array, simply by calling the clone method:

    public class CloneDemo6 {
        public static void main(String args[]) {
            int vec1[] = new int[]{1, 2, 3};
            int vec2[] = (int[])vec1.clone();
            System.out.println(vec2[0] + " " + vec2[1] +
                " " + vec2[2]);
        }
    }

A final important point about cloning: it's a way to create and 
initialize new objects, but it's not the same as calling a 
constructor. One example of why this distinction matters
concerns blank finals, that is, uninitialized fields declared 
"final", that can only be given a value in constructors. Here's 
an example of blank final usage:

    public class CloneDemo7 {
    
        private int a;
        private int b;
        private final long c;
    
        public CloneDemo7(int a, int b) {
            this.a = a;
            this.b = b;
            this.c = System.currentTimeMillis();
        }

        public static void main(String args[]) {
            CloneDemo7 obj = new CloneDemo7(37, 47);
        }
    }

In the CloneDemo7 constructor, a final field "c" is given 
a timestamp value that is obtained from the system clock. What 
if you want to copy an object of this type? Object.clone copies 
all the fields, but you want the timestamp field to be set to the 
current system clock value. However, if the field is final, you 
can only set the field in a constructor, not in a clone method. 
Here's a illustration of this issue:

    public class CloneDemo8 {
    
        private int a;
        private int b;
        private final long c;
    
        public CloneDemo8(int a, int b) {
            this.a = a;
            this.b = b;
            this.c = System.currentTimeMillis();
        }
    
        public CloneDemo8(CloneDemo8 obj) {
            this.a = obj.a;
            this.b = obj.b;
            this.c = System.currentTimeMillis();
        }
    
        public Object clone() throws CloneNotSupportedException {
            //this.c = System.currentTimeMillis();
            return super.clone();
        }

        public static void main(String args[]) {
            CloneDemo8 obj = new CloneDemo8(37, 47);
            CloneDemo8 obj2 = new CloneDemo8(obj);
        }
    }

If you uncomment the line that attempts to set final field
"c", the program won't compile. So instead of using clone to set
the field, the program uses a copy constructor. A copy 
constructor is a constructor for the class that takes an object 
reference of the same class type, and implements the appropriate 
copying logic.

You might think you could solve this problem by not using blank
finals, and instead simply use final fields that are immediately 
initialized with the system time, like this:

    class A implements Cloneable {
        final long x = System.currentTimeMillis();
        public Object clone() {
            try {
                return super.clone();
            }
            catch (CloneNotSupportedException e) {
                throw new InternalError(e.toString());
            }
        }
    }
    
    public class CloneDemo9 {
        public static void main(String args[]) {
            A obj1 = new A();
            // sleep 100 ms before doing clone,
            // to ensure unique timestamp
            try {
                Thread.sleep(100);
            }
            catch (InterruptedException e) {
                System.err.println(e);
            }
            A obj2 = (A)obj1.clone();
            System.out.println(obj1.x + " " + obj2.x);
        }
    }

This doesn't work either. When you run the program, you see that 
obj1.x and obj2.x have the same value. This indicates that normal 
object initialization is not done when an object is cloned, and 
you can't set the value of final fields within the clone method. 
So if simple copying is not appropriate for initializing a field, 
you can't declare it final. Or else you need to use copy 
constructors as a cloning alternative.

For more information about object cloning, see Section 3.9, 
Cloning Objects, and  Section 2.5.1, Constructors, in "The Java 
Programming Language Third Edition" by Arnold, Gosling, and 
Holmes 
(http://java.sun.com/docs/books/javaprog/thirdedition/).

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
USING THE SERIALIZABLE FIELDS API

Serialization is a mechanism in which arbitrary objects can be
converted into a byte stream, to be saved to disk or transmitted
across a network. The byte stream can later be deserialized to
reconstitute the object. You make the default serialization 
mechanism available simply by declaring that your class 
implements the java.io.Serializable marker interface.

This tip presents a two-part example that uses what is called 
the Serializable Fields API. If you've worked with serialization, 
you know that it's possible to override the default mechanism for 
a given class. One way you can do this is by defining your own 
readObject and writeObject methods. The Serializable Fields API
ties in to this mechanism.

Imagine a simple application where you would like to keep a 
cumulative total of the number of weeks that have elapsed from 
some starting point. You save this information in object form to 
a file, and periodically add to the total. Here's a program that 
does this:

    import java.io.*;
    
    class ElapsedTime implements Serializable {
    
        static final long serialVersionUID = 892420644258946182L;
    
        private double numweeks;
    
        // read a serialized object
        private void readObject(ObjectInputStream in)
          throws IOException, ClassNotFoundException {
            ObjectInputStream.GetField fields = in.readFields();
            numweeks = fields.get("numweeks", 0.0);
        }
    
        // write a serialized object
        private void writeObject(ObjectOutputStream out)
          throws IOException {
            ObjectOutputStream.PutField fields = out.putFields();
            fields.put("numweeks", numweeks);
            out.writeFields();
        }
    
        // constructor
        public ElapsedTime() {
            numweeks = 0.0;
        }
    
        // add to the elapsed time
        public void addTime(double t) {
            numweeks += t;
        }
    
        // return the elapsed time
        public double getTime() {
            return numweeks;
        }
    }
    
    public class FieldDemo1 {
    
        private static final String DATAFILE = "data.ser";
    
        public static void main(String args[])
          throws IOException, ClassNotFoundException {
    
            if (args.length != 1) {
                System.err.println("missing command line argument");
                System.exit(1);
            }
    
            ElapsedTime et = null;
    
            // read serialized object if data file exists,
            // else create a new object
    
            if (new File(DATAFILE).exists()) {
                FileInputStream fis = new FileInputStream(DATAFILE);
                ObjectInputStream ois = new ObjectInputStream(fis);
                et = (ElapsedTime)ois.readObject();
                fis.close();
            }
            else {
                et = new ElapsedTime();
            }
    
            // update the elapsed time
    
            et.addTime(Double.parseDouble(args[0]));
    
            // write the serialized object
    
            FileOutputStream fos = new FileOutputStream(DATAFILE);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(et);
            oos.close();
    
            System.out.println("Elapsed time is " +
                et.getTime() + " weeks");
        }
    }

The FieldDemo1 program reads a serialized object of class 
ElapsedTime from the data file (data.ser). If the file doesn't 
exist, the program creates a default object. Then the program 
updates the cumulative time, and writes the object back to the 
file. You can use the program like this:

    $ javac FieldDemo1.java
    $ java FieldDemo1 10
    $ java FieldDemo1 20

This sequence adds two values (10 and 20) to the cumulative
total of elapsed weeks.

The program's use of serialization is straightforward, 
except for two things. The first is the serialVersionUID field
(this will be discussed further a little later in the tip). The 
second thing is the use of the Serializable Fields API.

The FieldDemo1 program defines readObject and writeObject 
methods. These methods interact with serialization by
specifying field names on which to operate. For example, one of 
the calls is:

    numweeks = fields.get("numweeks", 0.0);

This interface is quite different than the usual mechanism, 
where the fields of the object are set for you. Using a custom
readObject method and the Serializable Fields API provide an 
alternative so that you can specify fields by name. The fields 
can be accessed in any order. Also, the default value specified 
to the get method is used if the input stream does not contain 
an explicit value for the field.

The interface used in writeObject is similar. The program uses
putFields to set up a buffer, and then it can put serializable 
field values into the buffer in any order. The program uses 
writeFields to write the buffer to the stream. Any fields that 
have not been set are given default values (0, false, null) 
appropriate to the field type.

Note that the use of the Serializable Fields API in the above 
example is not required. It offers a different type of interface 
that helps to make clear exactly what is going on with the 
setting of the various fields. But there are tradeoffs here, for 
example, in performance. Using this API when you don't need to, 
might not always be a good idea.

Suppose that you've been using the above approach for a while
in your application, and then you decide to represent elapsed
time as a number of days instead of weeks. It's pretty easy to
change the ElapsedTime class to do this. But what happens to all 
the ElapsedTime objects that have been serialized and are sitting
around in databases and files?

Suppose too, for the moment, that your original version of
ElapsedTime did not declare the static "serialVersionUID" field.
If you change ElapsedTime, then when you try to deserialize old
objects of this class, you get an InvalidClassException. This is
because serialization uses what is known as a "serial version
UID" to detect compatible versions of a given class. The serial
version UID is a 64-bit number whose default value (if not
explicitly declared) is a hash of the class name, interface
names, member signatures, and miscellaneous class attributes.
The serial version UID for a class is written when instances of
the class are serialized. If the class changes, the default
serial version UID value changes as well. When ObjectInputStream
deserializes objects, it checks to make sure that serial version
UIDs contained in the incoming stream match those of the classes
loaded in the receiving JVM*. If a mismatch occurs, then an
InvalidClassException is thrown.

If you change a class, but you don't want the serialization
mechanism to complain, you need to declare your own
serialVersionUID field in the class. If the earlier version of
the class did not already declare a serial version UID value,
then you can determine its implicit (default) serial version UID
value by using the serialvertool, which is included in the
standard JDK distribution:

    $ javac FieldDemo1.java
    $ serialver -classpath . ElapsedTime
    
If the previous version of the class already explicitly declared
a serial version UID value, then you can simply leave the
declaration in place with the same value.    

As a general rule, it's always a good idea to declare explicit
serial version UID values for serializable classes. There are two
reasons for doing that:

o Runtime computation of the serial version UID hash is expensive
  in terms of performance. If you don't declare a serial version
  UID value, then serialization must do it for you at runtime.

o The default serial version UID computation algorithm is
  extremely sensitive to class changes, including those that  
  might result from legal differences in compiler implementations.
  For instance, a given serializable nested class might have
  different default serial version UID values depending on which
  javac was used to compile it. That's because javac must add
  synthetic class members to implement nested classes, and
  differences in these synthetic class members are picked up by
  the serial version UID calculation.

Let's look at the second demo program, and then finish the
explanation:

    import java.io.*;
    
    class ElapsedTime implements Serializable {
    
        static final long serialVersionUID = 892420644258946182L;
    
        // list of fields to be serialized; this list
        // need not match the actual fields in ElapsedTime
        private static final ObjectStreamField
        serialPersistentFields[] = {
            new ObjectStreamField("numweeks", Double.TYPE)
        };
    
        // transient (unserialized) field for this object
        private transient double numdays;
    
        // read a serialized object
        private void readObject(ObjectInputStream in)
          throws IOException, ClassNotFoundException {
            ObjectInputStream.GetField fields = in.readFields();
            numdays = fields.get("numweeks", 0.0) * 7.0;
        }
    
        // write a serialized object
        private void writeObject(ObjectOutputStream out)
          throws IOException {
            ObjectOutputStream.PutField fields = out.putFields();
            fields.put("numweeks", numdays / 7.0);
            out.writeFields();
        }
    
        // constructor
        public ElapsedTime() {
            numdays = 0.0;
        }
    
        // add to the elapsed time
        public void addTime(double t) {
            numdays += t;
        }
    
        // get the elapsed time
        public double getTime() {
            return numdays;
        }
    }
    
    public class FieldDemo2 {
    
        private static final String DATAFILE = "data.ser";
    
        public static void main(String args[])
          throws IOException, ClassNotFoundException {
    
            if (args.length != 1) {
                System.err.println("missing command line argument");
                System.exit(1);
            }
    
            ElapsedTime et = null;
    
            // read serialized object if it exists, else
            // create a new object
    
            if (new File(DATAFILE).exists()) {
                FileInputStream fis = new FileInputStream(DATAFILE);
                ObjectInputStream ois = new ObjectInputStream(fis);
                et = (ElapsedTime)ois.readObject();
                fis.close();
            }
            else {
                et = new ElapsedTime();
            }
    
            // add to the elapsed time
    
            et.addTime(Double.parseDouble(args[0]));
    
            // write the serialized object
    
            FileOutputStream fos = new FileOutputStream(DATAFILE);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(et);
            oos.close();
    
            System.out.println("Elapsed time is " +
                et.getTime() + " days");
        }
    }

Both programs have the serialVersionUID field set to the same 
value, so serialization will consider the two ElapsedTime class 
versions to be compatible with each other.

But there's a problem here. If elapsed time was represented as
a number of weeks, but now as a number of days, then the field 
numweeks in the serialized objects would simply be wrong. You 
could change it to days, but that makes all the existing objects 
invalid.

The solution to this problem is to keep the original 
representation in the serialized objects, but use the
Serializable Field API to translate between days and weeks. That 
is, continue to read and write objects containing numweeks, but 
then multiply or divide by 7.0 to give the number of days.

Notice that numdays is the field used in the new version of the
ElapsedTime class. But this is not the same as the field in 
actual serialized objects, which is numweeks. If you try to put 
fields by name, using the Serializable Fields API, and the name 
doesn't actually represent a serializable field in the class, you
get an exception. So you need to go a step further, by adding the 
following declaration to the ElapsedTime class:

    private static final ObjectStreamField
    serialPersistentFields[] = {
        new ObjectStreamField("numweeks", Double.TYPE)
    };
    
This is a special field that the serialization mechanism knows
about. By setting this field, you can override the default set of
serializable fields. The fields in this list do not have to be 
part of the current class definition for the class that you're 
trying to serialize. The ObjectStreamField constructor takes 
a field name and a field type, with the type represented using 
java.lang.Class.

So what you've done is serialize numweeks, and declared numdays 
as transient, that is, not subject to serialization. Using this 
approach, it is possible for both old and new versions of the 
ElapsedTime class to read and write serialized objects. In other 
words, you can read existing objects using the new class version, 
write out updated objects, and then use the old class version to 
read and write them as well.

Here's an example sequence that illustrates this idea (before you 
try this sequence, be sure to remove any copy of data.ser that
you have around):

    javac FieldDemo1.java
    java FieldDemo1 10
    
    javac FieldDemo2.java
    java FieldDemo2 14
    
    javac FieldDemo1.java
    java FieldDemo1 5
    
    javac FieldDemo2.java
    java FieldDemo2 28

When the sequence is executed, the result is:

    Elapsed time is 10.0 weeks
    Elapsed time is 84.0 days
    Elapsed time is 17.0 weeks
    Elapsed time is 147.0 days

This output demonstrates that both object views (weeks and days) 
are represented. For example, the first program adds 10 weeks to
the total, or 70 days. Then the second program is called to add 14
days, and the total is now 84 days.


For more information about using the Serializable Fields API, see 
the tutorial "Using Serialization and the Serializable Fields API"
(http://java.sun.com/j2se/1.3/docs/guide/serialization/
examples/altimpl/index3.html), and the Java Object
Serialization Specification
(http://java.sun.com/j2se/1.3/docs/guide/serialization/
spec/serialTOC.doc.html).

.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .

- NOTE

Sun respects your online time and privacy. The Java Developer 
Connection mailing lists are used for internal Sun Microsystems(tm) 
purposes only. You have received this email because you elected 
to subscribe. To unsubscribe, go to the Subscriptions page 
(http://developer.java.sun.com/subscription/), uncheck the 
appropriate checkbox, and click the Update button.


- SUBSCRIBE

To subscribe to a JDC newsletter mailing list, go to the 
Subscriptions page (http://developer.java.sun.com/subscription/), 
choose the newsletters you want to subscribe to, and click Update.


- FEEDBACK
Comments? Send your feedback on the JDC Tech Tips to:

jdc-webmaster@sun.com


- ARCHIVES
You'll find the JDC Tech Tips archives at:

http://java.sun.com/jdc/TechTips/index.html


- COPYRIGHT
Copyright 2001 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.

This document is protected by copyright. For more information, see:

http://java.sun.com/jdc/copyright.html


- LINKS TO NON-SUN SITES
The JDC Tech Tips may provide, or third parties may provide, 
links to other Internet sites or resources. Because Sun has no 
control over such sites and resources, You acknowledge and agree 
that Sun is not responsible for the availability of such external 
sites or resources, and does not endorse and is not responsible 
or liable for any Content, advertising, products, or other 
materials on or available from such sites or resources. Sun will 
not be responsible or liable, directly or indirectly, for any 
damage or loss caused or alleged to be caused by or in connection 
with use of or reliance on any such Content, goods or services 
available on or through any such site or resource.


This issue of the JDC Tech Tips is written by Glen McCluskey.

JDC Tech Tips 
March 6, 2001

* As used in this document, the terms "Java virtual machine" or 
  "JVM" mean a virtual machine for the Java platform. 

Sun, Sun Microsystems, Java, and Java Developer Connection are 
trademarks or registered trademarks of Sun Microsystems, Inc. 
in the United States and other countries.








