Best way to store Country codes, names, and Continent in Java

19,868

Solution 1

There is 246 countries in ISO 3166, you might get a relay big enum on back of this. I prefer to use XML file with list of countries, you can download one from http://www.iso.org/ and load them (e.g. when app is starting). Than, as you need them in GWT load them in back as RPC call, but remember to cache those (some kind of lazy loading) so you wont finish with loading them each time. I think this would be anyway better than holding them in code, as you will finish with loading full list each time module is accessed, even if user will not need to use this list.

So you need something which will hold country:

public class Country
{
    private final String name;
    private final String code;

    public Country(String name, String code)
    {
        this.name = name;
        this.code = code;
    }

    public String getName()
    {
        return name;
    }

    public String getCode()
    {
        return code;
    }

    public boolean equals(Object obj)
    {
        if (this == obj)
        {
            return true;
        }
        if (obj == null || getClass() != obj.getClass())
        {
            return false;
        }

        Country country = (Country) obj;

        return code.equals(country.code);
    }

    public int hashCode()
    {
        return code.hashCode();
    }
}

For GWT this class would need to implement IsSerializable. And you can load those, on server side using:

import java.util.ArrayList;
import java.util.List;
import java.io.InputStream;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

public class CountriesService
{
    private static final String EL_COUNTRY = "ISO_3166-1_Entry";
    private static final String EL_COUNTRY_NAME = "ISO_3166-1_Country_name";
    private static final String EL_COUNTRY_CODE = "ISO_3166-1_Alpha-2_Code_element";
    private List<Country> countries = new ArrayList<Country>();

    public CountriesService(InputStream countriesList)
    {
        parseCountriesList(countriesList);
    }

    public List<Country> getCountries()
    {
        return countries;
    }

    private void parseCountriesList(InputStream countriesList)
    {
        countries.clear();
        try
        {
            Document document = parse(countriesList);
            Element root = document.getRootElement();
            //noinspection unchecked
            Iterator<Element> i = root.elementIterator(EL_COUNTRY);
            while (i.hasNext())
            {
                Element countryElement = i.next();
                Element countryName = countryElement.element(EL_COUNTRY_NAME);
                Element countryCode = countryElement.element(EL_COUNTRY_CODE);

                String countryname = countryName.getText();
                countries.add(new Country(countryname, countryCode.getText()));
            }
        }
        catch (DocumentException e)
        {
            log.error(e, "Cannot read countries list");
        }
        catch (IOException e)
        {
            log.error(e, "Cannot read countries list");
        }
    }

    public static Document parse(InputStream inputStream) throws DocumentException
    {
        SAXReader reader = new SAXReader();
        return reader.read(inputStream);
    }
}

Of course, if you need to find country by ISO 2 letter code you might wont to change List to Map probably. If, as you mentioned, you need separate countries by continent, you might extend XML from ISO 3166 and add your own elements. Just check their (ISO website) license.

Solution 2

Just make an enum called Country. Java enums can have properties, so there's your country code and name. For the continent, you pobably want another enum.

public enum Continent
{
    AFRICA, ANTARCTICA, ASIA, AUSTRALIA, EUROPE, NORTH_AMERICA, SOUTH_AMERICA
}

public enum Country
{
    ALBANIA("AL", "Albania", Continent.EUROPE),
    ANDORRA("AN", "Andorra", Continent.EUROPE),
    ...

    private String code;
    private String name;
    private Continent continent;

    // get methods go here    

    private Country(String code, String name, Continent continent)
    {
        this.code = code;
        this.name = name;
        this.continent = continent;
    }
}

As for storing and access, one Map for each of the fields you'll be searching for, keyed on that that field, would be the standard solution. Since you have multiple values for the continent, you'll either have to use a Map<?, List<Country>>, or a Multimap implementation e.g. from Apache commons.

Solution 3

If you frequently need to do a lookup by continent, I'd simply make a series of immutable lists, one for each continent, and populate them accordingly. The list of country-data for a continent is probably not going to change frequently enough for the cost of rebuilding such an array to be rebuilt when something needs to be altered.

Also, if you're willing to do the country-continent classification manually, the rest is automatic and can be done programmatically.

Solution 4

The easiest way to do this is to create the country/continent structure in Java using a map (or whatever collection) and then persist it using XStream

This will create an XML representation of the collection, and you can read than into your process very easily and convert it back to the same collection type that you initially created. Furthermore, because it's XML, you can easily edit it outside of code. i.e. just in a text editor.

See the XStream tutorial for more info.

Share:
19,868
gregory boero.teyssier
Author by

gregory boero.teyssier

https://ali.actor

Updated on July 28, 2022

Comments

  • gregory boero.teyssier
    gregory boero.teyssier almost 2 years

    I want to have a List or Array of some sort, storing this information about each country:

    • 2 letter code
    • Country name such as Brazil
    • Continent/region of the world such as Eastern Europe, North America, etc.

    I will classify each country into the region/continent manually (but if there exists a way to do this automatically, do let me know). This question is about how to store and access the countries. For example, I want to be able to retrieve all the countries in North America.

    I don't want to use local text files or such because this project will be converted to javascript using Google Web Toolkit. But storing in an Enum or another resource file of some sort, keeping it separate from the rest of the code, is what I'm really after.

    • mjn
      mjn about 15 years
      Some countries belong to two continents (in Europe Turkey, Russia and Kazakhstan), a M:N relationship
    • gregory boero.teyssier
      gregory boero.teyssier about 15 years
      thats their problem :). no one will want to use those 3 particular countries anyway, and if they do they can look them up alphabetically rather than by region
    • mjn
      mjn about 15 years
      so you know which countries will not be 'used'? you must be a real expert ...
  • gregory boero.teyssier
    gregory boero.teyssier about 15 years
    What do you mean by an immutable list? Can you share some code? Sorry, I'm a newbie to java
  • John Feminella
    John Feminella about 15 years
    An immutable list is just one whose contents can't be changed (see ReadOnlyCollection). However, I like Michael's approach better, and I think you should take that one instead of mine.
  • Hugo
    Hugo about 15 years
    You should be clear that you're storing the "en-US" version of the "name";)
  • gregory boero.teyssier
    gregory boero.teyssier about 15 years
    One question, how could i convert North_america to North america? Is there a tostring() method for enums?
  • Hugo
    Hugo about 15 years
    Well, they can be combined: enum Country { ... static { for(Continent c : Continent.values()) for(Country cc : Country.values()) if(cc.getContinent() == c) byContinent.get(c).add(cc) } /* converted with Collections.immutableList() and immutableMap() */ ... }
  • Michael Borgwardt
    Michael Borgwardt about 15 years
    @mjustin: Oops... @ClickUpvot: All classes have a toString() method inherited from Object. Simply override it to return the name. And of course you'd also have a getName() accessor, I omitted that for brevity.
  • Andreas Petersson
    Andreas Petersson about 15 years
    and when there is a new country.. emergency deploy?
  • Michael Borgwardt
    Michael Borgwardt about 15 years
    If that's a problem, you need a better deployment process. Besides, where would YOU put it? "Configuration files" aren't fundamentally harder to change than code nor exempt from potentially destructive errors.
  • whiskeysierra
    whiskeysierra almost 13 years
    Be aware of countries that belong to two continents like Russia and Turkey.