From 8a9f55265405ad7218bd9ef4a597e865b6ab3541 Mon Sep 17 00:00:00 2001 From: Christoph Weitkamp Date: Tue, 29 Oct 2019 11:14:03 +0100 Subject: [PATCH 1/2] Added data structure for exposing country descriptions Signed-off-by: Christoph Weitkamp --- .../java/de/jollyday/AbstractI18nObject.java | 93 ++++++++++++ src/main/java/de/jollyday/City.java | 126 ++++++++++++++++ src/main/java/de/jollyday/Country.java | 116 +++++++++++++++ src/main/java/de/jollyday/Holiday.java | 69 ++------- src/main/java/de/jollyday/Region.java | 135 ++++++++++++++++++ .../java/de/jollyday/util/ResourceUtil.java | 129 ++++++++++++++--- 6 files changed, 594 insertions(+), 74 deletions(-) create mode 100644 src/main/java/de/jollyday/AbstractI18nObject.java create mode 100644 src/main/java/de/jollyday/City.java create mode 100644 src/main/java/de/jollyday/Country.java create mode 100644 src/main/java/de/jollyday/Region.java diff --git a/src/main/java/de/jollyday/AbstractI18nObject.java b/src/main/java/de/jollyday/AbstractI18nObject.java new file mode 100644 index 00000000..f4a0f1c9 --- /dev/null +++ b/src/main/java/de/jollyday/AbstractI18nObject.java @@ -0,0 +1,93 @@ +/** + * Copyright 2019 Sven Diedrichsen + * + * 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. + */ +package de.jollyday; + +import java.util.Locale; + +import de.jollyday.util.ResourceUtil; + +/** + * Represents a localizable object. + * + * @author Christoph Weitkamp + * @version $Id: $ + */ +public abstract class AbstractI18nObject { + /** + * The calculated hashcode cached for performance. + */ + protected int hashCode = 0; + /** + * The properties key to retrieve the description with. + */ + protected final String propertiesKey; + + /** + * Utility for accessing resources. + */ + private final ResourceUtil resourceUtil = new ResourceUtil(); + + /** + * Constructs a country using the provided ISO code to retrieve the + * description with. + * + * @param propertiesKey + * a {@link java.lang.String} object. + */ + public AbstractI18nObject(String propertiesKey) { + super(); + this.propertiesKey = propertiesKey; + } + + /** + *

+ * Getter for the propertiesKey. + *

+ * + * @return the country properties key + */ + public String getPropertiesKey() { + return propertiesKey; + } + + /** + * The description read with the default locale. + * + * @return Description for this object + */ + public String getDescription() { + return resourceUtil.getHolidayDescription(Locale.getDefault(), propertiesKey); + } + + /** + * The description read with the provided locale. + * + * @param locale + * a {@link java.util.Locale} object. + * @return Description for this object + */ + public String getDescription(Locale locale) { + return resourceUtil.getHolidayDescription(locale, propertiesKey); + } + + @Override + public String toString() { + return propertiesKey + " (" + getDescription() + ")"; + } + + @Override + public abstract int hashCode(); +} diff --git a/src/main/java/de/jollyday/City.java b/src/main/java/de/jollyday/City.java new file mode 100644 index 00000000..1cde11d1 --- /dev/null +++ b/src/main/java/de/jollyday/City.java @@ -0,0 +1,126 @@ +/** + * Copyright 2019 Sven Diedrichsen + * + * 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. + */ +package de.jollyday; + +/** + * Represents a city and contains the city code and a localized description. + * + * @author Christoph Weitkamp + * @version $Id: $ + */ +public final class City extends AbstractI18nObject implements Comparable { + /** + * The ISO code to retrieve the description with. + */ + private final String isoCode; + /** + * The region code to retrieve the description with. + */ + private final String regionCode; + /** + * The city code to retrieve the description with. + */ + private final String code; + + /** + * Constructs a city using the provided code to retrieve the description + * with. + * + * @param isoCode + * a {@link java.lang.String} object. + * @param regionCode + * a {@link java.lang.String} object. + * @param code + * a {@link java.lang.String} object. + */ + public City(String isoCode, String regionCode, String code) { + super(isoCode + "." + regionCode + "." + code); + this.isoCode = isoCode; + this.regionCode = regionCode; + this.code = code; + } + + /** + *

+ * Getter for the field isoCode. + *

+ * + * @return the ISO code + */ + public String getISOCode() { + return isoCode; + } + + /** + *

+ * Getter for the field regionCode. + *

+ * + * @return the region code + */ + public String getRegionCode() { + return regionCode; + } + + /** + *

+ * Getter for the field code. + *

+ * + * @return the city code + */ + public String getCode() { + return code; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof City) { + City other = (City) obj; + return isoCode.equals(other.isoCode) && regionCode.equals(other.regionCode) && code.equals(other.code); + } + return false; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int hash = 1; + hash = hash * 31 + isoCode.hashCode(); + hash = hash * 31 + regionCode.hashCode(); + hash = hash * 31 + code.hashCode(); + hashCode = hash; + } + return hashCode; + } + + /** + * Compares this city to another city. + * + * The comparison is primarily based on the city code. + * + * @param other + * the other city to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + @Override + public int compareTo(City other) { + return code.compareTo(other.code); + } +} diff --git a/src/main/java/de/jollyday/Country.java b/src/main/java/de/jollyday/Country.java new file mode 100644 index 00000000..fc30901a --- /dev/null +++ b/src/main/java/de/jollyday/Country.java @@ -0,0 +1,116 @@ +/** + * Copyright 2019 Sven Diedrichsen + * + * 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. + */ +package de.jollyday; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Represents a country. It contains the ISO code, a localized description and a + * list of regions. + * + * @author Christoph Weitkamp + * @version $Id: $ + */ +public final class Country extends AbstractI18nObject implements Comparable { + /** + * The ISO code to retrieve the description with. + */ + private final String isoCode; + /** + * A list of regions inside this country + */ + private final Set regions = new HashSet<>(0); + + /** + * Constructs a country using the provided ISO code to retrieve the + * description with. + * + * @param isoCode + * a {@link java.lang.String} object. + */ + public Country(String isoCode) { + super(isoCode); + this.isoCode = isoCode; + } + + /** + *

+ * Getter for the field isoCode. + *

+ * + * @return the ISO code + */ + public String getISOCode() { + return isoCode; + } + + /** + * Adds a {@link Region} to this country. + * + * @param region + * a {@link Region} object. + */ + public void addRegion(Region region) { + regions.add(region); + } + + /** + * The {@link Region}s of this country. + * + * @return an unmodifiable set of {@link Region}s of this country + */ + public Set getRegions() { + return Collections.unmodifiableSet(regions); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Country) { + Country other = (Country) obj; + return isoCode.equals(other.isoCode) && regions.equals(other.regions); + } + return false; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int hash = 1; + hash = hash * 31 + isoCode.hashCode(); + hashCode = hash; + } + return hashCode; + } + + /** + * Compares this country to another country. + * + * The comparison is primarily based on the ISO code. + * + * @param other + * the other country to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + @Override + public int compareTo(Country other) { + return isoCode.compareTo(other.isoCode); + } +} diff --git a/src/main/java/de/jollyday/Holiday.java b/src/main/java/de/jollyday/Holiday.java index 047355c2..72f89fb6 100644 --- a/src/main/java/de/jollyday/Holiday.java +++ b/src/main/java/de/jollyday/Holiday.java @@ -15,40 +15,24 @@ */ package de.jollyday; -import de.jollyday.util.ResourceUtil; - import java.time.LocalDate; -import java.util.Locale; /** - * Represents the holiday and contains the actual date and an localized + * Represents the holiday and contains the actual date and a localized * description. * * @author Sven Diedrichsen * @version $Id: $ */ -public final class Holiday implements Comparable { - /** - * The calculated hashcode cached for performance. - */ - private int hashCode = 0; +public final class Holiday extends AbstractI18nObject implements Comparable { /** * The date the holiday occurs. */ private final LocalDate date; - /** - * The properties key to retrieve the description with. - */ - private final String propertiesKey; - /** * The type of holiday. e.g. official holiday or not. - * */ - private final HolidayType type; - /** - * Utility for accessing resources. */ - private final ResourceUtil resourceUtil = new ResourceUtil(); + private final HolidayType type; /** * Constructs a holiday for a date using the provided properties key to @@ -62,10 +46,9 @@ public final class Holiday implements Comparable { * a {@link de.jollyday.HolidayType} object. */ public Holiday(LocalDate date, String propertiesKey, HolidayType type) { - super(); - this.type = type; + super(propertiesKey == null ? "" : propertiesKey); this.date = date; - this.propertiesKey = propertiesKey == null ? "" : propertiesKey; + this.type = type; } /** @@ -79,37 +62,6 @@ public LocalDate getDate() { return date; } - /** - *

- * Getter for the field propertiesKey. - *

- * - * @return the holidays properties key - */ - public String getPropertiesKey() { - return propertiesKey; - } - - /** - * The description read with the default locale. - * - * @return Description for this holiday - */ - public String getDescription() { - return resourceUtil.getHolidayDescription(Locale.getDefault(), getPropertiesKey()); - } - - /** - * The description read with the provided locale. - * - * @param locale - * a {@link java.util.Locale} object. - * @return Description for this holiday - */ - public String getDescription(Locale locale) { - return resourceUtil.getHolidayDescription(locale, getPropertiesKey()); - } - @Override public boolean equals(Object obj) { if (obj == this) { @@ -117,8 +69,7 @@ public boolean equals(Object obj) { } if (obj instanceof Holiday) { Holiday other = (Holiday) obj; - return other.date.equals(this.date) && other.propertiesKey.equals(this.propertiesKey) - && type.equals(other.type); + return date.equals(other.date) && propertiesKey.equals(other.propertiesKey) && type.equals(other.type); } return false; } @@ -152,13 +103,15 @@ public HolidayType getType() { /** * Compares this holiday to another holiday. * - * The comparison is primarily based on the date, from earliest to latest by using the LocalDate comparator. + * The comparison is primarily based on the date, from earliest to latest by + * using the LocalDate comparator. * - * @param other the other holiday to compare to, not null + * @param other + * the other holiday to compare to, not null * @return the comparator value, negative if less, positive if greater */ @Override public int compareTo(Holiday other) { - return this.getDate().compareTo(other.getDate()); + return date.compareTo(other.date); } } diff --git a/src/main/java/de/jollyday/Region.java b/src/main/java/de/jollyday/Region.java new file mode 100644 index 00000000..ed5c9a88 --- /dev/null +++ b/src/main/java/de/jollyday/Region.java @@ -0,0 +1,135 @@ +/** + * Copyright 2019 Sven Diedrichsen + * + * 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. + */ +package de.jollyday; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Represents a region. It contains the region code, a localized description and + * a list of cities. + * + * @author Christoph Weitkamp + * @version $Id: $ + */ +public final class Region extends AbstractI18nObject implements Comparable { + /** + * The ISO code to retrieve the description with. + */ + private final String isoCode; + /** + * The code to retrieve the description with. + */ + private final String code; + /** + * A list of cities inside this region + */ + private final Set cities = new HashSet<>(0); + + /** + * Constructs a region using the provided code to retrieve the description + * with. + * + * @param isoCode + * a {@link java.lang.String} object. + * @param code + * a {@link java.lang.String} object. + */ + public Region(String isoCode, String code) { + super(isoCode + "." + code); + this.isoCode = isoCode; + this.code = code; + } + + /** + *

+ * Getter for the field isoCode. + *

+ * + * @return the ISO code + */ + public String getISOCode() { + return isoCode; + } + + /** + *

+ * Getter for the field code. + *

+ * + * @return the region code + */ + public String getCode() { + return code; + } + + /** + * Adds a {@link City} to this region. + * + * @param city + * a {@link City} object. + */ + public void addCity(City city) { + cities.add(city); + } + + /** + * The {@link City Cities} of this region. + * + * @return an unmodifiable set of {@link City Cities} of this region + */ + public Set getCities() { + return Collections.unmodifiableSet(cities); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Region) { + Region other = (Region) obj; + return isoCode.equals(other.isoCode) && code.equals(other.code) && cities.equals(other.cities); + } + return false; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int hash = 1; + hash = hash * 31 + isoCode.hashCode(); + hash = hash * 31 + code.hashCode(); + hashCode = hash; + } + return hashCode; + } + + /** + * Compares this region to another region. + * + * The comparison is primarily based on the region code. + * + * @param other + * the other region to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + @Override + public int compareTo(Region other) { + return code.compareTo(other.code); + } +} diff --git a/src/main/java/de/jollyday/util/ResourceUtil.java b/src/main/java/de/jollyday/util/ResourceUtil.java index d22910e7..39fd9f7d 100644 --- a/src/main/java/de/jollyday/util/ResourceUtil.java +++ b/src/main/java/de/jollyday/util/ResourceUtil.java @@ -16,9 +16,19 @@ package de.jollyday.util; import java.net.URL; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import de.jollyday.City; +import de.jollyday.Country; +import de.jollyday.Region; + /** *

* ResourceUtil class. @@ -32,6 +42,9 @@ public class ResourceUtil { * Property prefix for country descriptions. */ private static final String COUNTRY_PROPERTY_PREFIX = "country.description"; + private static final int COUNTRY_INDEX = 2; + private static final int REGION_INDEX = 3; + private static final int CITY_INDEX = 4; /** * Property prefix for holiday descriptions. */ @@ -66,7 +79,8 @@ public class ResourceUtil { * The description read with the default locale. * * @return holiday description using default locale. - * @param key a {@link java.lang.String} object. + * @param key + * a {@link java.lang.String} object. */ public String getHolidayDescription(String key) { return getHolidayDescription(Locale.getDefault(), key); @@ -75,9 +89,11 @@ public String getHolidayDescription(String key) { /** * The description read with the provided locale. * - * @param locale a {@link java.util.Locale} object. + * @param locale + * a {@link java.util.Locale} object. * @return holiday description using the provided locale. - * @param key a {@link java.lang.String} object. + * @param key + * a {@link java.lang.String} object. */ public String getHolidayDescription(Locale locale, String key) { return getDescription(HOLIDAY_PROPERTY_PREFIX + "." + key, getHolidayDescriptions(locale)); @@ -89,7 +105,8 @@ public String getHolidayDescription(Locale locale, String key) { *

* * @return the description - * @param key a {@link java.lang.String} object. + * @param key + * a {@link java.lang.String} object. */ public String getCountryDescription(String key) { return getCountryDescription(Locale.getDefault(), key); @@ -98,8 +115,10 @@ public String getCountryDescription(String key) { /** * Returns the hierarchies description text from the resource bundle. * - * @param l Locale to return the description text for. - * @param key a {@link java.lang.String} object. + * @param l + * Locale to return the description text for. + * @param key + * a {@link java.lang.String} object. * @return Description text */ public String getCountryDescription(Locale l, String key) { @@ -119,19 +138,93 @@ public Set getISOCodes() { ResourceBundle countryDescriptions = getCountryDescriptions(Locale.getDefault()); for (String property : Collections.list(countryDescriptions.getKeys())) { String[] split = property.split("\\."); - if (split.length > 2) { - codes.add(split[2].toLowerCase()); + if (split.length > COUNTRY_INDEX) { + codes.add(split[COUNTRY_INDEX].toLowerCase()); } } return codes; } + /** + * TODO + * + * @return + */ + public Set getCountries() { + Set contries = new HashSet<>(); + ResourceBundle countryDescriptions = getCountryDescriptions(Locale.getDefault()); + for (String property : Collections.list(countryDescriptions.getKeys())) { + String[] split = property.split("\\."); + if (split.length > COUNTRY_INDEX) { + contries.add(new Country(split[COUNTRY_INDEX].toLowerCase())); + } + } + return contries; + } + + /** + * TODO + * + * @return + */ + public Map> getRegions(String isoCode) { + Map> regions = new HashMap<>(); + ResourceBundle countryDescriptions = getCountryDescriptions(Locale.getDefault()); + for (String property : Collections.list(countryDescriptions.getKeys())) { + String[] split = property.split("\\."); + if (split.length > REGION_INDEX) { + String countryCode = split[COUNTRY_INDEX].toLowerCase(); + if (isoCode.equals(countryCode)) { + Region region = new Region(countryCode, split[REGION_INDEX].toLowerCase()); + if (regions.containsKey(region.getISOCode())) { + regions.get(region.getISOCode()).add(region); + } else { + Set internalRegions = new HashSet<>(); + internalRegions.add(region); + regions.put(region.getISOCode(), internalRegions); + } + } + } + } + return regions; + } + + /** + * TODO + * + * @return + */ + public Map> getCitys(String regionCode) { + Map> cities = new HashMap<>(); + ResourceBundle countryDescriptions = getCountryDescriptions(Locale.getDefault()); + for (String property : Collections.list(countryDescriptions.getKeys())) { + String[] split = property.split("\\."); + if (split.length > CITY_INDEX) { + String internalRegionCode = split[REGION_INDEX].toLowerCase(); + if (regionCode.equals(internalRegionCode)) { + City city = new City(split[COUNTRY_INDEX].toLowerCase(), internalRegionCode, + split[CITY_INDEX].toLowerCase()); + if (cities.containsKey(city.getRegionCode())) { + cities.get(city.getRegionCode()).add(city); + } else { + Set internalCities = new HashSet<>(); + internalCities.add(city); + cities.put(city.getRegionCode(), internalCities); + } + } + } + } + return cities; + } + /** * Returns the description from the resource bundle if the key is contained. * It will return 'undefined' otherwise. * - * @param key the key to get the description from - * @param bundle the bundle to get the description + * @param key + * the key to get the description from + * @param bundle + * the bundle to get the description * @return description the description behind the key */ private String getDescription(String key, final ResourceBundle bundle) { @@ -145,7 +238,8 @@ private String getDescription(String key, final ResourceBundle bundle) { * Returns the eventually cached ResourceBundle for the holiday * descriptions. * - * @param l Locale to retrieve the descriptions for. + * @param l + * Locale to retrieve the descriptions for. * @return ResourceBundle containing the descriptions for the locale. */ private ResourceBundle getHolidayDescriptions(Locale l) { @@ -156,7 +250,8 @@ private ResourceBundle getHolidayDescriptions(Locale l) { * Returns the eventually cached ResourceBundle for the holiday * descriptions. * - * @param l Locale to retrieve the descriptions for. + * @param l + * Locale to retrieve the descriptions for. * @return ResourceBundle containing the descriptions for the locale. */ private ResourceBundle getCountryDescriptions(Locale l) { @@ -166,7 +261,8 @@ private ResourceBundle getCountryDescriptions(Locale l) { /** * Returns the eventually cached ResourceBundle for the descriptions. * - * @param l Locale to retrieve the descriptions for. + * @param l + * Locale to retrieve the descriptions for. * @return ResourceBundle containing the descriptions for the locale. */ private ResourceBundle getResourceBundle(Locale l, Map resourceCache, String filePrefix) { @@ -180,7 +276,8 @@ private ResourceBundle getResourceBundle(Locale l, Map r /** * Returns the resource by URL. * - * @param resourceName the name/path of the resource to load + * @param resourceName + * the name/path of the resource to load * @return the URL to the resource */ public URL getResource(String resourceName) { @@ -188,7 +285,7 @@ public URL getResource(String resourceName) { URL resource = classLoadingUtil.getClassloader().getResource(resourceName); return resource == null ? this.getClass().getClassLoader().getResource(resourceName) : resource; } catch (Exception e) { - throw new IllegalStateException("Cannot load resource: " + resourceName, e); + throw new IllegalStateException("Cannot load resource: " + resourceName, e); } } From cb566db06bc7e1b15f5a6341852dfad6d2f433b0 Mon Sep 17 00:00:00 2001 From: Christoph Weitkamp Date: Wed, 30 Oct 2019 13:43:22 +0100 Subject: [PATCH 2/2] Added CountryDescriptionProvider and unit tests Signed-off-by: Christoph Weitkamp --- .../java/de/jollyday/AbstractI18nObject.java | 6 +- src/main/java/de/jollyday/Country.java | 28 ++-- src/main/java/de/jollyday/Holiday.java | 6 + src/main/java/de/jollyday/Region.java | 28 ++-- .../CountryDescriptionProvider.java | 64 +++++++++ .../impl/CountryDescriptionProviderImpl.java | 130 ++++++++++++++++++ .../java/de/jollyday/util/ResourceUtil.java | 85 +----------- .../country_descriptions_de.properties | 2 +- src/test/java/de/jollyday/CityTest.java | 32 +++++ src/test/java/de/jollyday/CountryTest.java | 27 ++++ src/test/java/de/jollyday/RegionTest.java | 29 ++++ .../CountryDescriptionProviderImplTest.java | 51 +++++++ 12 files changed, 387 insertions(+), 101 deletions(-) create mode 100644 src/main/java/de/jollyday/configuration/CountryDescriptionProvider.java create mode 100644 src/main/java/de/jollyday/configuration/impl/CountryDescriptionProviderImpl.java create mode 100644 src/test/java/de/jollyday/CityTest.java create mode 100644 src/test/java/de/jollyday/CountryTest.java create mode 100644 src/test/java/de/jollyday/RegionTest.java create mode 100644 src/test/java/de/jollyday/configuration/impl/CountryDescriptionProviderImplTest.java diff --git a/src/main/java/de/jollyday/AbstractI18nObject.java b/src/main/java/de/jollyday/AbstractI18nObject.java index f4a0f1c9..82a4aee1 100644 --- a/src/main/java/de/jollyday/AbstractI18nObject.java +++ b/src/main/java/de/jollyday/AbstractI18nObject.java @@ -38,7 +38,7 @@ public abstract class AbstractI18nObject { /** * Utility for accessing resources. */ - private final ResourceUtil resourceUtil = new ResourceUtil(); + protected final ResourceUtil resourceUtil = new ResourceUtil(); /** * Constructs a country using the provided ISO code to retrieve the @@ -69,7 +69,7 @@ public String getPropertiesKey() { * @return Description for this object */ public String getDescription() { - return resourceUtil.getHolidayDescription(Locale.getDefault(), propertiesKey); + return getDescription(Locale.getDefault()); } /** @@ -80,7 +80,7 @@ public String getDescription() { * @return Description for this object */ public String getDescription(Locale locale) { - return resourceUtil.getHolidayDescription(locale, propertiesKey); + return resourceUtil.getCountryDescription(locale, propertiesKey); } @Override diff --git a/src/main/java/de/jollyday/Country.java b/src/main/java/de/jollyday/Country.java index fc30901a..5764d0b3 100644 --- a/src/main/java/de/jollyday/Country.java +++ b/src/main/java/de/jollyday/Country.java @@ -15,9 +15,10 @@ */ package de.jollyday; +import java.util.Collection; import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.HashMap; +import java.util.Map; /** * Represents a country. It contains the ISO code, a localized description and a @@ -32,9 +33,9 @@ public final class Country extends AbstractI18nObject implements Comparable regions = new HashSet<>(0); + private final Map regions = new HashMap<>(0); /** * Constructs a country using the provided ISO code to retrieve the @@ -66,16 +67,27 @@ public String getISOCode() { * a {@link Region} object. */ public void addRegion(Region region) { - regions.add(region); + regions.put(region.getCode(), region); } /** * The {@link Region}s of this country. * - * @return an unmodifiable set of {@link Region}s of this country + * @return an unmodifiable list of all {@link Region}s of this country */ - public Set getRegions() { - return Collections.unmodifiableSet(regions); + public Collection getRegions() { + return Collections.unmodifiableCollection(regions.values()); + } + + /** + * Returns a specific {@link Region}s of this country. + * + * @param regionCode + * {@link java.lang.String}the region code. + * @return a {@link Region} (could be null) + */ + public Region getRegion(String regionCode) { + return regions.get(regionCode); } @Override diff --git a/src/main/java/de/jollyday/Holiday.java b/src/main/java/de/jollyday/Holiday.java index 72f89fb6..1e51b424 100644 --- a/src/main/java/de/jollyday/Holiday.java +++ b/src/main/java/de/jollyday/Holiday.java @@ -16,6 +16,7 @@ package de.jollyday; import java.time.LocalDate; +import java.util.Locale; /** * Represents the holiday and contains the actual date and a localized @@ -74,6 +75,11 @@ public boolean equals(Object obj) { return false; } + @Override + public String getDescription(Locale locale) { + return resourceUtil.getHolidayDescription(locale, propertiesKey); + } + @Override public int hashCode() { if (hashCode == 0) { diff --git a/src/main/java/de/jollyday/Region.java b/src/main/java/de/jollyday/Region.java index ed5c9a88..71305e75 100644 --- a/src/main/java/de/jollyday/Region.java +++ b/src/main/java/de/jollyday/Region.java @@ -15,9 +15,10 @@ */ package de.jollyday; +import java.util.Collection; import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.HashMap; +import java.util.Map; /** * Represents a region. It contains the region code, a localized description and @@ -36,9 +37,9 @@ public final class Region extends AbstractI18nObject implements Comparable cities = new HashSet<>(0); + private final Map cities = new HashMap<>(0); /** * Constructs a region using the provided code to retrieve the description @@ -84,16 +85,27 @@ public String getCode() { * a {@link City} object. */ public void addCity(City city) { - cities.add(city); + cities.put(city.getCode(), city); } /** * The {@link City Cities} of this region. * - * @return an unmodifiable set of {@link City Cities} of this region + * @return an unmodifiable list of all {@link City Cities} of this region */ - public Set getCities() { - return Collections.unmodifiableSet(cities); + public Collection getCities() { + return Collections.unmodifiableCollection(cities.values()); + } + + /** + * Returns a specific {@link City} of this region. + * + * @param cityCode + * {@link java.lang.String} the city code. + * @return a {@link City} (could be null) + */ + public City getCity(String cityCode) { + return cities.get(cityCode); } @Override diff --git a/src/main/java/de/jollyday/configuration/CountryDescriptionProvider.java b/src/main/java/de/jollyday/configuration/CountryDescriptionProvider.java new file mode 100644 index 00000000..5c7fe74a --- /dev/null +++ b/src/main/java/de/jollyday/configuration/CountryDescriptionProvider.java @@ -0,0 +1,64 @@ +/** + * Copyright 2019 Sven Diedrichsen + * + * 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. + */ +package de.jollyday.configuration; + +import java.util.Collection; + +import de.jollyday.City; +import de.jollyday.Country; +import de.jollyday.Region; + +/** + * The interface for Jollyday country description provider. + * + * @author Christoph Weitkamp + * @version $Id: $ + */ +public interface CountryDescriptionProvider { + + /** + * Returns a list of all {@link Country Countries}. + * + * @return an unmodifiable list of all {@link Country Countries} + */ + Collection getCountries(); + + /** + * Returns a list of all {@link Region}s inside a given {@link Country}. + * Could be empty. + * + * @param isoCode + * {@link java.lang.String} sthe country code to retrieve the + * regions from. + * @return an unmodifiable list of all {@link Region}s inside the given + * {@link Country} + * @throws IllegalArgumentException + */ + Collection getRegions(String isoCode); + + /** + * Returns a list of all {@link City Cities} from a given {@link Region}. + * Could be empty. + * + * @param regionCode + * {@link java.lang.String} the region code to retrieve the + * cities from. + * @return an unmodifiable list of all {@link City Cities} inside the given + * {@link Region} + * @throws IllegalArgumentException + */ + Collection getCities(String regionCode); +} diff --git a/src/main/java/de/jollyday/configuration/impl/CountryDescriptionProviderImpl.java b/src/main/java/de/jollyday/configuration/impl/CountryDescriptionProviderImpl.java new file mode 100644 index 00000000..13c69e1d --- /dev/null +++ b/src/main/java/de/jollyday/configuration/impl/CountryDescriptionProviderImpl.java @@ -0,0 +1,130 @@ +/** + * Copyright 2019 Sven Diedrichsen + * + * 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. + */ +package de.jollyday.configuration.impl; + +import static de.jollyday.util.ResourceUtil.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; + +import de.jollyday.City; +import de.jollyday.Country; +import de.jollyday.Region; +import de.jollyday.configuration.CountryDescriptionProvider; +import de.jollyday.util.ResourceUtil; + +/** + * An implementation of the Jollyday country description provider. + * + * @author Christoph Weitkamp + * @version $Id: $ + */ +public class CountryDescriptionProviderImpl implements CountryDescriptionProvider { + /** + * Internal map of ISO code / country + */ + final Map countries = new HashMap<>(); + /** + * Internal map of ISO code / list of regions + */ + final Map> regions = new HashMap<>(); + /** + * Internal map of region code / list of cities + */ + final Map> cities = new HashMap<>(); + + private final ResourceUtil resourceUtil = new ResourceUtil(); + + public CountryDescriptionProviderImpl() { + parseCountryDescriptions(); + } + + @Override + public Collection getCountries() { + return Collections.unmodifiableCollection(countries.values()); + } + + @Override + public Collection getRegions(String isoCode) { + if (regions.containsKey(isoCode)) { + return Collections.unmodifiableCollection(regions.get(isoCode)); + } else { + throw new IllegalArgumentException(String.format("Country with ISO code '%s' does not exist.", isoCode)); + } + } + + @Override + public Collection getCities(String regionCode) { + if (cities.containsKey(regionCode)) { + return Collections.unmodifiableCollection(cities.get(regionCode)); + } else { + throw new IllegalArgumentException(String.format("Region with code '%s' does not exist.", regionCode)); + } + } + + private void parseCountryDescriptions() { + // temporary map of region code / region + final Map tempRegions = new HashMap<>(); + final ResourceBundle countryDescriptions = resourceUtil.getCountryDescriptions(Locale.getDefault()); + for (String property : Collections.list(countryDescriptions.getKeys())) { + final String[] split = property.split("\\."); + if (split.length > COUNTRY_INDEX) { + final String countryCode = split[COUNTRY_INDEX].toLowerCase(); + switch (split.length) { + case COUNTRY_INDEX + 1: + countries.put(countryCode, new Country(countryCode)); + break; + case REGION_INDEX + 1: + final Region region = new Region(countryCode, split[REGION_INDEX].toLowerCase()); + if (regions.containsKey(region.getISOCode())) { + regions.get(region.getISOCode()).add(region); + } else { + List localRegions = new ArrayList<>(0); + localRegions.add(region); + regions.put(region.getISOCode(), localRegions); + } + if (countries.containsKey(region.getISOCode())) { + countries.get(region.getISOCode()).addRegion(region); + } + tempRegions.put(region.getCode(), region); + break; + case CITY_INDEX + 1: + final City city = new City(countryCode, split[REGION_INDEX].toLowerCase(), + split[CITY_INDEX].toLowerCase()); + if (cities.containsKey(city.getRegionCode())) { + cities.get(city.getRegionCode()).add(city); + } else { + List localCities = new ArrayList<>(0); + localCities.add(city); + cities.put(city.getRegionCode(), localCities); + } + if (tempRegions.containsKey(city.getRegionCode())) { + tempRegions.get(city.getRegionCode()).addCity(city); + } + break; + default: + break; + } + } + } + } +} diff --git a/src/main/java/de/jollyday/util/ResourceUtil.java b/src/main/java/de/jollyday/util/ResourceUtil.java index 39fd9f7d..3b882519 100644 --- a/src/main/java/de/jollyday/util/ResourceUtil.java +++ b/src/main/java/de/jollyday/util/ResourceUtil.java @@ -17,7 +17,6 @@ import java.net.URL; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; @@ -25,10 +24,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import de.jollyday.City; -import de.jollyday.Country; -import de.jollyday.Region; - /** *

* ResourceUtil class. @@ -42,9 +37,9 @@ public class ResourceUtil { * Property prefix for country descriptions. */ private static final String COUNTRY_PROPERTY_PREFIX = "country.description"; - private static final int COUNTRY_INDEX = 2; - private static final int REGION_INDEX = 3; - private static final int CITY_INDEX = 4; + public static final int COUNTRY_INDEX = 2; + public static final int REGION_INDEX = 3; + public static final int CITY_INDEX = 4; /** * Property prefix for holiday descriptions. */ @@ -145,78 +140,6 @@ public Set getISOCodes() { return codes; } - /** - * TODO - * - * @return - */ - public Set getCountries() { - Set contries = new HashSet<>(); - ResourceBundle countryDescriptions = getCountryDescriptions(Locale.getDefault()); - for (String property : Collections.list(countryDescriptions.getKeys())) { - String[] split = property.split("\\."); - if (split.length > COUNTRY_INDEX) { - contries.add(new Country(split[COUNTRY_INDEX].toLowerCase())); - } - } - return contries; - } - - /** - * TODO - * - * @return - */ - public Map> getRegions(String isoCode) { - Map> regions = new HashMap<>(); - ResourceBundle countryDescriptions = getCountryDescriptions(Locale.getDefault()); - for (String property : Collections.list(countryDescriptions.getKeys())) { - String[] split = property.split("\\."); - if (split.length > REGION_INDEX) { - String countryCode = split[COUNTRY_INDEX].toLowerCase(); - if (isoCode.equals(countryCode)) { - Region region = new Region(countryCode, split[REGION_INDEX].toLowerCase()); - if (regions.containsKey(region.getISOCode())) { - regions.get(region.getISOCode()).add(region); - } else { - Set internalRegions = new HashSet<>(); - internalRegions.add(region); - regions.put(region.getISOCode(), internalRegions); - } - } - } - } - return regions; - } - - /** - * TODO - * - * @return - */ - public Map> getCitys(String regionCode) { - Map> cities = new HashMap<>(); - ResourceBundle countryDescriptions = getCountryDescriptions(Locale.getDefault()); - for (String property : Collections.list(countryDescriptions.getKeys())) { - String[] split = property.split("\\."); - if (split.length > CITY_INDEX) { - String internalRegionCode = split[REGION_INDEX].toLowerCase(); - if (regionCode.equals(internalRegionCode)) { - City city = new City(split[COUNTRY_INDEX].toLowerCase(), internalRegionCode, - split[CITY_INDEX].toLowerCase()); - if (cities.containsKey(city.getRegionCode())) { - cities.get(city.getRegionCode()).add(city); - } else { - Set internalCities = new HashSet<>(); - internalCities.add(city); - cities.put(city.getRegionCode(), internalCities); - } - } - } - } - return cities; - } - /** * Returns the description from the resource bundle if the key is contained. * It will return 'undefined' otherwise. @@ -254,7 +177,7 @@ private ResourceBundle getHolidayDescriptions(Locale l) { * Locale to retrieve the descriptions for. * @return ResourceBundle containing the descriptions for the locale. */ - private ResourceBundle getCountryDescriptions(Locale l) { + public ResourceBundle getCountryDescriptions(Locale l) { return getResourceBundle(l, COUNTRY_DESCRIPTIONS_CACHE, COUNTRY_DESCRIPTIONS_FILE_PREFIX); } diff --git a/src/main/resources/descriptions/country_descriptions_de.properties b/src/main/resources/descriptions/country_descriptions_de.properties index 44bcc5d4..4cc167a2 100644 --- a/src/main/resources/descriptions/country_descriptions_de.properties +++ b/src/main/resources/descriptions/country_descriptions_de.properties @@ -28,7 +28,7 @@ country.description.au.nt = N\u00F6rdliches Territorium country.description.au.qld = Queensland country.description.au.sa = S\u00FCd-Australien country.description.au.tas = Tasmanien -country.description.au.tas.ho = Hobard Area +country.description.au.tas.ho = Hobart country.description.au.tas.nh = Non-Hobard Area country.description.au.vic = Victoria country.description.au.wa = West-Australien diff --git a/src/test/java/de/jollyday/CityTest.java b/src/test/java/de/jollyday/CityTest.java new file mode 100644 index 00000000..d020419b --- /dev/null +++ b/src/test/java/de/jollyday/CityTest.java @@ -0,0 +1,32 @@ +package de.jollyday; + +import java.util.Locale; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class CityTest { + + private static final String COUNTRY_AUSTRALIA_KEY = "au"; + private static final String REGION_TASMANIA_KEY = "tas"; + private static final String CITY_HOBARD_AREA_KEY = "ho"; + + @Before + public void setup() { + Locale.setDefault(Locale.ENGLISH); + } + + @Test + public void testI18nWorksCorrectly() { + City region = new City(COUNTRY_AUSTRALIA_KEY, REGION_TASMANIA_KEY, CITY_HOBARD_AREA_KEY); + Assert.assertEquals(CITY_HOBARD_AREA_KEY, region.getCode()); + Assert.assertEquals(REGION_TASMANIA_KEY, region.getRegionCode()); + Assert.assertEquals(COUNTRY_AUSTRALIA_KEY, region.getISOCode()); + Assert.assertEquals(COUNTRY_AUSTRALIA_KEY + "." + REGION_TASMANIA_KEY + "." + CITY_HOBARD_AREA_KEY, + region.getPropertiesKey()); + + Assert.assertEquals("Hobard Area", region.getDescription()); + Assert.assertEquals("Hobart", region.getDescription(Locale.GERMAN)); + } +} diff --git a/src/test/java/de/jollyday/CountryTest.java b/src/test/java/de/jollyday/CountryTest.java new file mode 100644 index 00000000..616edcca --- /dev/null +++ b/src/test/java/de/jollyday/CountryTest.java @@ -0,0 +1,27 @@ +package de.jollyday; + +import java.util.Locale; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class CountryTest { + + private static final String COUNTRY_AUSTRALIA_KEY = "au"; + + @Before + public void setup() { + Locale.setDefault(Locale.ENGLISH); + } + + @Test + public void testI18nWorksCorrectly() { + Country country = new Country(COUNTRY_AUSTRALIA_KEY); + Assert.assertEquals(COUNTRY_AUSTRALIA_KEY, country.getISOCode()); + Assert.assertEquals(COUNTRY_AUSTRALIA_KEY, country.getPropertiesKey()); + + Assert.assertEquals("Australia", country.getDescription()); + Assert.assertEquals("Australien", country.getDescription(Locale.GERMAN)); + } +} diff --git a/src/test/java/de/jollyday/RegionTest.java b/src/test/java/de/jollyday/RegionTest.java new file mode 100644 index 00000000..b4015cbd --- /dev/null +++ b/src/test/java/de/jollyday/RegionTest.java @@ -0,0 +1,29 @@ +package de.jollyday; + +import java.util.Locale; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class RegionTest { + + private static final String COUNTRY_AUSTRALIA_KEY = "au"; + private static final String REGION_TASMANIA_KEY = "tas"; + + @Before + public void init() { + Locale.setDefault(Locale.ENGLISH); + } + + @Test + public void testI18nWorksCorrectly() { + Region region = new Region(COUNTRY_AUSTRALIA_KEY, REGION_TASMANIA_KEY); + Assert.assertEquals(REGION_TASMANIA_KEY, region.getCode()); + Assert.assertEquals(COUNTRY_AUSTRALIA_KEY, region.getISOCode()); + Assert.assertEquals(COUNTRY_AUSTRALIA_KEY + "." + REGION_TASMANIA_KEY, region.getPropertiesKey()); + + Assert.assertEquals("Tasmania", region.getDescription()); + Assert.assertEquals("Tasmanien", region.getDescription(Locale.GERMAN)); + } +} diff --git a/src/test/java/de/jollyday/configuration/impl/CountryDescriptionProviderImplTest.java b/src/test/java/de/jollyday/configuration/impl/CountryDescriptionProviderImplTest.java new file mode 100644 index 00000000..f9179103 --- /dev/null +++ b/src/test/java/de/jollyday/configuration/impl/CountryDescriptionProviderImplTest.java @@ -0,0 +1,51 @@ +package de.jollyday.configuration.impl; + +import java.util.Collection; + +import org.junit.Assert; +import org.junit.Test; + +import de.jollyday.City; +import de.jollyday.Country; +import de.jollyday.Region; +import de.jollyday.util.ResourceUtil; + +public class CountryDescriptionProviderImplTest { + + private static final String COUNTRY_AUSTRALIA_KEY = "au"; + private static final String REGION_TASMANIA_KEY = "tas"; + private static final String CITY_HOBARD_AREA_KEY = "ho"; + + private CountryDescriptionProviderImpl provider = new CountryDescriptionProviderImpl(); + private ResourceUtil resourceUtil = new ResourceUtil(); + + @Test + public void testParsingOfCountriesWorksCorrecly() { + Assert.assertNotEquals(0, provider.countries); + Assert.assertEquals(resourceUtil.getISOCodes().size(), provider.countries.size()); + + Collection countries = provider.getCountries(); + Assert.assertEquals(provider.countries.size(), countries.size()); + + Assert.assertTrue( + countries.stream().filter(c -> c.getISOCode().equals(COUNTRY_AUSTRALIA_KEY)).findFirst().isPresent()); + } + + public void testParsingOfRegionsWorksCorrectly() { + Assert.assertNotEquals(0, provider.regions); + + Collection regions = provider.getRegions(COUNTRY_AUSTRALIA_KEY); + Assert.assertEquals(8, regions.size()); + + Assert.assertTrue(regions.contains(new Region(COUNTRY_AUSTRALIA_KEY, REGION_TASMANIA_KEY))); + } + + public void testParsingOfCitiesWorksCorrectly() { + Assert.assertNotEquals(0, provider.cities); + + Collection cities = provider.getCities(REGION_TASMANIA_KEY); + Assert.assertEquals(2, cities.size()); + + Assert.assertTrue(cities.contains(new City(COUNTRY_AUSTRALIA_KEY, REGION_TASMANIA_KEY, CITY_HOBARD_AREA_KEY))); + } +}