/** * * Copyright 2009-2014 The MITRE Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ************************************************************************** * NOTICE * This software was produced for the U. S. Government under Contract No. * W15P7T-12-C-F600, and is subject to the Rights in Noncommercial Computer * Software and Noncommercial Computer Software Documentation Clause * 252.227-7014 (JUN 1995) * * (c) 2012 The MITRE Corporation. All Rights Reserved. * ************************************************************************** */ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| // // _____ ____ __ __ ///\ __`\ /\ _`\ /\ \__ /\ \__ //\ \ \/\ \ _____ __ ___ \ \,\L\_\ __ __ _\ \ ,_\ __ ___ \ \ ,_\ // \ \ \ \ \ /\ '__`\ /'__`\ /' _ `\ \/_\__ \ /'__`\/\ \/'\\ \ \/ /'__`\ /' _ `\\ \ \/ // \ \ \_\ \\ \ \L\ \/\ __/ /\ \/\ \ /\ \L\ \ /\ __/\/> </ \ \ \_ /\ \L\.\_ /\ \/\ \\ \ \_ // \ \_____\\ \ ,__/\ \____\\ \_\ \_\ \ `\____\\ \____\/\_/\_\ \ \__\\ \__/.\_\\ \_\ \_\\ \__\ // \/_____/ \ \ \/ \/____/ \/_/\/_/ \/_____/ \/____/\//\/_/ \/__/ \/__/\/_/ \/_/\/_/ \/__/ // \ \_\ // \/_/ // // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| // package org.opensextant.data; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.StringUtils; /** * * @author Marc C. Ubaldino, MITRE, ubaldino at mitre dot org */ public class Country extends Place { /** ISO 2-character country code */ public String CC_ISO2 = null; /** ISO 3-character country code */ public String CC_ISO3 = null; /** FIPS 10-4 2-character country code */ public String CC_FIPS = null; private String namenorm = null; /** Any list of country alias names. */ private final Set<String> aliases = new HashSet<>(); private final Set<String> regions = new HashSet<>(); private final List<Country> territories = new ArrayList<>(); /** Map of Geonames.org TZ and UTC offsets per country */ private final Map<String, TZ> tzdb = new HashMap<>(); private final Map<String, Double> timezones = new HashMap<>(); private final Map<String, Double> timezonesVariants = new HashMap<>(); private final ArrayList<String> languages = new ArrayList<>(); private final Set<String> languagesSet = new HashSet<>(); /** * A country abstraction that uses ISO 2-alpha as an ID, and any name given * as the Place.name * * @param iso2 * ISO 2-alpha code for this country * @param nm * Country name */ public Country(String iso2, String nm) { super(iso2, nm); CC_ISO2 = this.key; this.country_id = this.key; if (this.name != null) { namenorm = name.toLowerCase(); addAlias(name); } } /** * Return name normalized, e.g., lowercase, w/out diacritics. 's, etc. */ public String getNamenorm() { return namenorm; } /** * Country is also known as some list of aliases * * @param nm * Country name/alias */ public void addAlias(String nm) { aliases.add(nm); } /** * * @return set of aliases */ public Set<String> getAliases() { return aliases; } /** * Add a timezone and its offset. TZ labels vary, so variant labels are tracked as well. * uppercase, lowercase * * @param label * TZ label * @param utcOffset * floating point UTC offset in decimal hours. e.g., 7.5, -3.0 = (GMT-0300), etc. */ public void addTimezone(String label, double utcOffset) { timezones.put(label, utcOffset); String l = label.toLowerCase(); timezonesVariants.put(l, utcOffset); if (label.contains("/")) { String tz = l.split("/", 2)[1]; timezonesVariants.put(tz, utcOffset); } } /** * Refactor -- use JodaTime and the TZDB more formally. For now, tzdb tracks the timezone metadata. * * @param tz */ public void addTimezone(TZ tz) { String l = tz.label.toLowerCase(); timezones.put(tz.label, tz.utcOffset); timezonesVariants.put(l, tz.utcOffset); tzdb.put(tz.label, tz); tzdb.put(l, tz); if (tz.label.contains("/")) { String labelPart = l.split("/", 2)[1]; timezonesVariants.put(labelPart, tz.utcOffset); tzdb.put(labelPart, tz); } } public final static class TZ { public String label = null; public double utcOffset = Double.NaN; public double dstOffset = Double.NaN; public double rawOffset = Double.NaN; public boolean usesDST = false; public double dstDelta = 0; public TZ(String l, double utc, double dst, double raw) { label = l; utcOffset = utc; dstOffset = dst; rawOffset = raw; dstDelta = utcOffset - dstOffset; usesDST = dstDelta != 0; } /** * Parse error will be thrown on invalid data. * Nulls or empty fields are allowable. * @param l * @param utc * @param dst * @param raw */ public TZ(String l, String utc, String dst, String raw) { label = l; utcOffset = getValue(utc); dstOffset = getValue(dst); rawOffset = getValue(raw); dstDelta = utcOffset - dstOffset; usesDST = dstDelta != 0; } private Double getValue(String v) { if (StringUtils.isEmpty(v)) { return Double.NaN; } return Double.parseDouble(v); } public String toString() { return String.format("%s %d, %d", label, utcOffset, dstOffset); } } /** * * @param tz * any reasonable TZ label. Case-insensitive. * @return true if Country has this TZ */ public boolean containsTimezone(String tz) { if (tz == null) { return false; } return timezonesVariants.containsKey(tz.toLowerCase()); } /** * Test if this Country contains the UTC offset. Make sure you never * pass a default of 0 (GMT+0) in, unless you really mean GMT0. * No validation on your offset parameter is done. * * @param offset * UTC offset in hours. Valid values are -12.0 to 12.0 * @return true if this Country contains the UTC offset. */ public boolean containsUTCOffset(double offset) { for (TZ tz : tzdb.values()) { if (Math.abs(tz.utcOffset - offset) < 0.10) { return true; } } return false; } public boolean containsDSTOffset(double offset) { for (TZ tz : tzdb.values()) { if (Math.abs(tz.dstOffset - offset) < 0.10) { return true; } } return false; } /** * Country is also known as some list of aliases * * @param regionid * Region identifier or name. */ public void addRegion(String regionid) { regions.add(regionid); } /** * * @return set of regions in which this country belongs. */ public Set<String> getRegions() { return regions; } private boolean uniqueName = false; public void setUniqueName(boolean b) { uniqueName = b; } public boolean hasUniqueName() { return uniqueName; } public boolean isTerritory = false; @Override public String toString() { if (!isTerritory && !hasTerritories()) { return String.format("%s (%s,%s,%s)", getName(), CC_ISO3, CC_ISO2, CC_FIPS); } else { // Some other country claims this land as a territory. return String.format("%s territory of %s", getName(), CC_ISO3); } } /** * When adding languages, please add the primary language FIRST. * Languages may be langID or langID+locale. TODO: add separate attributes for locales. * * @param langid * language */ public void addLanguage(String langid) { if (!languagesSet.contains(langid)) { languages.add(langid); languagesSet.add(langid); } } /** * * @param langid * language ID * @return if language (identified by ID l) is spoken */ public boolean isSpoken(String langid) { if (langid == null) { return false; } return languagesSet.contains(langid.toLowerCase()); } /** * Certain island nations, areas, and territories that have ISO country codes may not have a language. * * @return first language in languages list (per addLanguage()); null if no languages present. */ public String getPrimaryLanguage() { if (languages.size() > 0) { return languages.get(0); } return null; } /** * * @param langid * @return true if language given matches primary language */ public boolean isPrimaryLanguage(String langid) { if (langid != null && languages.size() > 0) { return langid.equalsIgnoreCase(languages.get(0)); } return false; } /** * * @return collection of language IDs -- some may be unknown langIDs. */ public Collection<String> getLanguages() { return languages; } /** * A full list/map of all timezone labels mapped to UTC offsets present in this country. * * Reference: geonames.org timezone table has timezones.txt; See our GeonamesUtility for how * data is populated here on Country object. * * @return map of TZ labels to UTC offsets. */ public Map<String, Double> getAllTimezones() { return timezonesVariants; } /** * Return the full list of TZ. * @return */ public Map<String, TZ> getTZDatabase() { return tzdb; } public boolean hasTerritories() { return !territories.isEmpty(); } public void addTerritory(Country terr) { territories.add(terr); } /** * Territory ownership is defined only by the data fed to this API; * We do not make any political statements here. You can change the underlying flat file data * country-names-xxxx.csv anyway you want. * * @param n * @return true if this country owns the named territory. */ public boolean ownsTerritory(String n) { if (hasTerritories()) { for (Country C : territories) { if (n.equalsIgnoreCase(C.getName())) { return true; } } } return false; } /** * List the territories for this country. * Returns an empty list if no territories associated. * * @return */ public Collection<Country> getTerritories() { return territories; } }