This commit is contained in:
Jan-Philipp Luithardt
2026-02-08 19:58:44 +01:00
parent ab681d59bd
commit ded3e953ea
35 changed files with 1454 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
package prog.ex15.exercise.i18ncountries;
/**
* Categories for the country related knowledge.
*/
public enum Category {
TRAFFIC, FOOD, HOLIDAYS, STATISTICS
}

View File

@@ -0,0 +1,61 @@
package prog.ex15.exercise.i18ncountries;
import java.beans.PropertyChangeListener;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
/**
* Facade to a configuration class which simplifies I18N.
*/
public interface Configuration {
/**
* Returns the current Locale object.
*
* @return current Locale object
*/
Locale getLocale();
/**
* Sets the locale to a new value. This triggers that the ResourceBundles get reloaded.
*
* @param locale new locale
*/
void setLocale(Locale locale);
/**
* Returns the current ListResourceBundle object.
*
* @return current ListResourceBundle object
*/
ResourceBundle getTypicalBundle();
/**
* Returns the current PropertyResourceBundle object.
*
* @return current PropertyResourceBundle object
*/
ResourceBundle getMessageBundle();
/**
* Returns a map which associates the possible countries with their Locale objects.
*
* @return map with the associations of Country to Locale
*/
Map<Country, Locale> getCountry2LocaleMap();
/**
* Adds a PropertyChangeListener to the configuration object.
*
* @param listener listener to be added
*/
void addPropertyChangeListener(PropertyChangeListener listener);
/**
* Removes a PropertyChangeListener from the configuration object.
*
* @param listener listener to be removed
*/
void removePropertyChangeListener(PropertyChangeListener listener);
}

View File

@@ -0,0 +1,8 @@
package prog.ex15.exercise.i18ncountries;
/**
* Countries for the WelcomeToMyCountry project.
*/
public enum Country {
GERMANY, ENGLAND, NETHERLANDS, DENMARK
}

View File

@@ -0,0 +1,66 @@
package prog.ex15.exercise.i18ncountries;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* This container holds knowledge about a country related to different categories.
*/
public class CountryKnowledgeContainer {
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(CountryKnowledgeContainer.class);
Map<Category, List<String>> categoryKnowledgeMap;
/**
* Creates an empty container.
*/
public CountryKnowledgeContainer() {
clear();
}
/**
* Adds a new entry to the container.
*
* @param category Category the knowledge is related to. The category must be NotNull.
* @param knowledge Knowledge string. The string must be NotNull.
* @throws IllegalArgumentException if either the category or the string is null
*/
public void addKnowledge(Category category, String knowledge) throws IllegalArgumentException {
if (category == null) {
throw new IllegalArgumentException("category is null reference.");
}
if (knowledge == null) {
throw new IllegalArgumentException("knowledge is null reference.");
}
List<String> knowledgeList = categoryKnowledgeMap.get(category);
knowledgeList.add(knowledge);
}
/**
* Returns a List of strings containing the knowledge, related to the given category.
*
* @param category selected category. The category must be NotNull.
* @return List of strings containing the knowledge, related to the given category
* @throws IllegalArgumentException if the category is null
*/
public List<String> getKnowledge(Category category) throws IllegalArgumentException {
if (category == null) {
throw new IllegalArgumentException("category is null reference.");
}
return categoryKnowledgeMap.get(category);
}
/**
* (Re)sets the data structures of the container.
*/
public void clear() {
categoryKnowledgeMap = new HashMap<>();
for (Category category : Category.values()) {
categoryKnowledgeMap.put(category, new ArrayList<>());
}
}
}

View File

@@ -0,0 +1,11 @@
package prog.ex15.exercise.i18ncountries;
/**
* Fills a CountryKnowledgeContainer.
*/
public interface KnowledgeGenerator {
/**
* Fills knowledge into the container.
*/
void fillContainer(CountryKnowledgeContainer container);
}

View File

@@ -0,0 +1,46 @@
package prog.ex15.exercise.i18ncountries;
import java.time.LocalDate;
/**
* Interface to make the creation of a ListResourceBundle more reliable.
*/
public interface TypicalCountry {
String VELOCITY = "velocity";
String VELOCITY_UNIT = "velocity-unit";
String POPULATION = "population";
String MOST_IMPORTANT_HOLIDAY_DATE = "most-important-holiday-date";
String MOST_IMPORTANT_HOLIDAY_NAME = "most-important-holiday-name";
String MOST_FAMOUS_MEAL = "most-famous-meal";
/**
* Setter for the maximum velocity on streets.
*
* @param velocity maximum allowed speed. If there is no maximum, the recommended velocity
* should be used.
* @param unit unit for the velocity, e.g. "km/h" in Europe, "mph" in USA
*/
void setVelocity(int velocity, String unit);
/**
* Number of people living in this country.
*
* @param population number of people
*/
void setPopulation(int population);
/**
* Most famous meal the country is known for.
*
* @param mostFamousMeal Name of the most famous meal
*/
void setMostFamousMeal(String mostFamousMeal);
/**
* Most important holiday in this country.
*
* @param date date of the current year the holiday takes place
* @param holidayName Name of the holiday
*/
void setMostImportantHoliday(LocalDate date, String holidayName);
}

View File

@@ -0,0 +1,24 @@
package prog.ex15.monolingual;
import prog.ex15.exercise.i18ncountries.Category;
import prog.ex15.exercise.i18ncountries.CountryKnowledgeContainer;
import prog.ex15.exercise.i18ncountries.KnowledgeGenerator;
/**
* Fills knowledge into a CountryKnowledgeContainer. I18N? Never heard.
*/
public class StupidKnowledgeGenerator implements KnowledgeGenerator {
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(StupidKnowledgeGenerator.class);
@Override
public void fillContainer(CountryKnowledgeContainer container) {
container.clear();
container.addKnowledge(Category.TRAFFIC, "Maximum speed on highways is 70 mph.");
container.addKnowledge(Category.FOOD, "Our most prominent food is Fish and Chips.");
container.addKnowledge(Category.HOLIDAYS,
"Our most important holiday is Brexit Day (Joke) on January, the 1, 2022.");
container.addKnowledge(Category.STATISTICS, "Our population is 66.500.000");
}
}

View File

@@ -0,0 +1,40 @@
package prog.ex15.monolingual.gui;
import java.util.List;
import javafx.scene.control.Accordion;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.VBox;
import prog.ex15.exercise.i18ncountries.Category;
import prog.ex15.exercise.i18ncountries.CountryKnowledgeContainer;
/**
* JavaFX component presenting the content of a CountryKnowledgeContainer.
*/
public class FxKnowledgePresenter extends Accordion {
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(FxKnowledgePresenter.class);
CountryKnowledgeContainer countryKnowledgeContainer;
public FxKnowledgePresenter(final CountryKnowledgeContainer countryKnowledgeContainer) {
this.countryKnowledgeContainer = countryKnowledgeContainer;
fillAccordion();
}
private void fillAccordion() {
this.getPanes().clear();
for (Category category : Category.values()) {
TitledPane titledPane = new TitledPane();
titledPane.setText(category.toString());
List<String> knowledgeList = countryKnowledgeContainer.getKnowledge(category);
VBox box = new VBox();
for (String string : knowledgeList) {
box.getChildren().add(new Label(string));
logger.info("Adding label " + string);
}
titledPane.setContent(box);
this.getPanes().add(titledPane);
}
}
}

View File

@@ -0,0 +1,26 @@
package prog.ex15.monolingual.gui;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import prog.ex15.exercise.i18ncountries.CountryKnowledgeContainer;
import prog.ex15.monolingual.StupidKnowledgeGenerator;
/**
* Main to launch the WelcomeToMyCountry content in a separate application.
*/
public class MonolingualWelcomeLauncher extends Application {
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(MonolingualWelcomeLauncher.class);
@Override
public void start(final Stage stage) throws Exception {
logger.debug("start: {}", stage);
CountryKnowledgeContainer container = new CountryKnowledgeContainer();
StupidKnowledgeGenerator generator = new StupidKnowledgeGenerator();
generator.fillContainer(container);
FxKnowledgePresenter presenter = new FxKnowledgePresenter(container);
stage.setScene(new Scene(presenter, 400, 300));
stage.show();
}
}

View File

@@ -0,0 +1,73 @@
package prog.ex15.solution.i18ncountries;
import prog.ex15.exercise.i18ncountries.Category;
import prog.ex15.exercise.i18ncountries.CountryKnowledgeContainer;
import prog.ex15.exercise.i18ncountries.KnowledgeGenerator;
import prog.ex15.exercise.i18ncountries.TypicalCountry;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;
import java.util.ResourceBundle;
/**
* Simple, straight-forward implementation of the KnowledgeGenerator interface for multiple
* countries.
*/
public class I18nKnowledgeGenerator implements KnowledgeGenerator {
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(I18nKnowledgeGenerator.class);
@Override
public void fillContainer(CountryKnowledgeContainer container) {
ResourceBundle resourceBundle = SingletonConfiguration.getInstance().getMessageBundle();
container.clear();
SingletonConfiguration config = SingletonConfiguration.getInstance();
ResourceBundle msg = config.getMessageBundle();
ResourceBundle data = config.getTypicalBundle();
Locale locale = config.getLocale();
int velocity = (Integer) data.getObject(TypicalCountry.VELOCITY);
String unit = (String) data.getObject(TypicalCountry.VELOCITY_UNIT);
int population = (Integer) data.getObject(TypicalCountry.POPULATION);
LocalDate holidayDate = (LocalDate) data.getObject(TypicalCountry.MOST_IMPORTANT_HOLIDAY_DATE);
String holidayName = (String) data.getObject(TypicalCountry.MOST_IMPORTANT_HOLIDAY_NAME);
String meal = (String) data.getObject(TypicalCountry.MOST_FAMOUS_MEAL);
DateTimeFormatter dateFormatter = DateTimeFormatter
.ofLocalizedDate(FormatStyle.FULL)
.withLocale(locale);
NumberFormat numberFormatter = NumberFormat.getNumberInstance(locale);
String trafficText = MessageFormat.format(
msg.getString("traffic.maximum.speed.highways"),
velocity,
unit
);
container.addKnowledge(Category.TRAFFIC, trafficText);
String foodText = MessageFormat.format(
msg.getString("food.most.prominent.food"),
meal
);
container.addKnowledge(Category.FOOD, foodText);
String holidayText = MessageFormat.format(
msg.getString("holiday.most.important.holiday"),
holidayName,
dateFormatter.format(holidayDate)
);
container.addKnowledge(Category.HOLIDAYS, holidayText);
String statisticsText = MessageFormat.format(
msg.getString("statistics.population"),
numberFormatter.format(population)
);
container.addKnowledge(Category.STATISTICS, statisticsText);
}
}

View File

@@ -0,0 +1,89 @@
package prog.ex15.solution.i18ncountries;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import prog.ex15.exercise.i18ncountries.Configuration;
import prog.ex15.exercise.i18ncountries.Country;
import prog.ex15.exercise.i18ncountries.TypicalCountry;
/**
* Singleton-based implementation of the Configuration interface.
*/
public class SingletonConfiguration implements Configuration {
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(SingletonConfiguration.class);
private static SingletonConfiguration instance = new SingletonConfiguration();
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private Locale locale;
private ResourceBundle resourceBundle;
private ResourceBundle typicalBundle;
public static SingletonConfiguration getInstance() {
return instance;
}
@Override
public Locale getLocale() {
return this.locale;
}
@Override
public void setLocale(final Locale locale) {
Locale newLocale = Objects.requireNonNullElse(locale, Locale.UK);
this.resourceBundle = ResourceBundle.getBundle("bundles/i18ncountries", newLocale);
this.typicalBundle = ResourceBundle.getBundle("prog.ex15.solution.i18ncountries.TypicalBundle", newLocale);
Locale oldLocale = this.locale;
this.locale = newLocale;
pcs.firePropertyChange("locale", oldLocale, this.locale);
}
@Override
public ResourceBundle getTypicalBundle() {
return this.typicalBundle;
}
@Override
public ResourceBundle getMessageBundle() {
return this.resourceBundle;
}
@Override
public Map<Country, Locale> getCountry2LocaleMap() {
return Arrays.stream(Country.values())
.collect(Collectors.toMap(
country -> country,
country -> switch (country) {
case GERMANY -> Locale.GERMANY;
case ENGLAND -> Locale.UK;
case NETHERLANDS -> new Locale("nl", "NL");
case DENMARK -> new Locale("da", "DK");
}
));
}
@Override
public void addPropertyChangeListener(final PropertyChangeListener listener) {
if(listener == null) {
throw new NullPointerException("This Listener is null!");
}
pcs.addPropertyChangeListener(listener);
}
@Override
public void removePropertyChangeListener(final PropertyChangeListener listener) {
if(listener == null) {
throw new NullPointerException("This Listener is null!");
}
pcs.removePropertyChangeListener(listener);
}
}

View File

@@ -0,0 +1,21 @@
package prog.ex15.solution.i18ncountries;
import prog.ex15.exercise.i18ncountries.TypicalCountry;
import java.time.LocalDate;
import java.util.ListResourceBundle;
public class TypicalBundle extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{TypicalCountry.VELOCITY, 70},
{TypicalCountry.VELOCITY_UNIT, "mph"},
{TypicalCountry.POPULATION, 69300000},
{TypicalCountry.MOST_IMPORTANT_HOLIDAY_DATE, LocalDate.of(2026, 1, 31)},
{TypicalCountry.MOST_IMPORTANT_HOLIDAY_NAME, "Brexit Day (Joke)"},
{TypicalCountry.MOST_FAMOUS_MEAL, "fish and chips"}
};
}
}

View File

@@ -0,0 +1,49 @@
package prog.ex15.solution.i18ncountries;
import prog.ex15.exercise.i18ncountries.TypicalCountry;
import java.time.LocalDate;
public abstract class TypicalBundleBase implements TypicalCountry {
protected int velocity;
protected String velocityUnit;
protected int population;
protected LocalDate holidayDate;
protected String holidayName;
protected String mostFamousMeal;
@Override
public void setVelocity(int velocity, String unit) {
this.velocity = velocity;
this.velocityUnit = unit;
}
@Override
public void setPopulation(int population) {
this.population = population;
}
@Override
public void setMostFamousMeal(String mostFamousMeal) {
this.mostFamousMeal = mostFamousMeal;
}
@Override
public void setMostImportantHoliday(LocalDate date, String holidayName) {
this.holidayDate = date;
this.holidayName = holidayName;
}
public Object[][] contents() {
return new Object[][] {
{VELOCITY, velocity},
{VELOCITY_UNIT, velocityUnit},
{POPULATION, population},
{MOST_IMPORTANT_HOLIDAY_DATE, holidayDate},
{MOST_IMPORTANT_HOLIDAY_NAME, holidayName},
{MOST_FAMOUS_MEAL, mostFamousMeal}
};
}
}

View File

@@ -0,0 +1,21 @@
package prog.ex15.solution.i18ncountries;
import prog.ex15.exercise.i18ncountries.TypicalCountry;
import java.time.LocalDate;
import java.util.ListResourceBundle;
public class TypicalBundle_da_DK extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{TypicalCountry.VELOCITY, 130},
{TypicalCountry.VELOCITY_UNIT, "km/h"},
{TypicalCountry.POPULATION, 6000000},
{TypicalCountry.MOST_IMPORTANT_HOLIDAY_DATE, LocalDate.of(2026, 6, 5)},
{TypicalCountry.MOST_IMPORTANT_HOLIDAY_NAME, "Grundlovsdag"},
{TypicalCountry.MOST_FAMOUS_MEAL, "knækbrød"}
};
}
}

View File

@@ -0,0 +1,21 @@
package prog.ex15.solution.i18ncountries;
import prog.ex15.exercise.i18ncountries.TypicalCountry;
import java.time.LocalDate;
import java.util.ListResourceBundle;
public class TypicalBundle_de_DE extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{TypicalCountry.VELOCITY, 130},
{TypicalCountry.VELOCITY_UNIT, "km/h"},
{TypicalCountry.POPULATION, 83500000},
{TypicalCountry.MOST_IMPORTANT_HOLIDAY_DATE, LocalDate.of(2026, 10, 3)},
{TypicalCountry.MOST_IMPORTANT_HOLIDAY_NAME, "Tag der Deutschen Einheit"},
{TypicalCountry.MOST_FAMOUS_MEAL, "Eisbein mit Sauerkraut"}
};
}
}

View File

@@ -0,0 +1,21 @@
package prog.ex15.solution.i18ncountries;
import prog.ex15.exercise.i18ncountries.TypicalCountry;
import java.time.LocalDate;
import java.util.ListResourceBundle;
public class TypicalBundle_en_GB extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{TypicalCountry.VELOCITY, 70},
{TypicalCountry.VELOCITY_UNIT, "mph"},
{TypicalCountry.POPULATION, 69300000},
{TypicalCountry.MOST_IMPORTANT_HOLIDAY_DATE, LocalDate.of(2026, 1, 31)},
{TypicalCountry.MOST_IMPORTANT_HOLIDAY_NAME, "Brexit Day (Joke)"},
{TypicalCountry.MOST_FAMOUS_MEAL, "fish and chips"}
};
}
}

View File

@@ -0,0 +1,21 @@
package prog.ex15.solution.i18ncountries;
import prog.ex15.exercise.i18ncountries.TypicalCountry;
import java.time.LocalDate;
import java.util.ListResourceBundle;
public class TypicalBundle_nl_NL extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{TypicalCountry.VELOCITY, 120},
{TypicalCountry.VELOCITY_UNIT, "km/h"},
{TypicalCountry.POPULATION, 18000000},
{TypicalCountry.MOST_IMPORTANT_HOLIDAY_DATE, LocalDate.of(2026, 4, 27)},
{TypicalCountry.MOST_IMPORTANT_HOLIDAY_NAME, "Koningsdag"},
{TypicalCountry.MOST_FAMOUS_MEAL, "pannenkoken"}
};
}
}

View File

@@ -0,0 +1,48 @@
package prog.ex15.solution.i18ncountries.gui;
import javafx.scene.control.Accordion;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.VBox;
import prog.ex15.exercise.i18ncountries.Category;
import prog.ex15.exercise.i18ncountries.CountryKnowledgeContainer;
import prog.ex15.solution.i18ncountries.SingletonConfiguration;
import java.util.List;
import java.util.ResourceBundle;
/**
* JavaFX component presenting the content of a CountryKnowledgeContainer.
*/
public class FxKnowledgePresenter extends Accordion {
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(FxKnowledgePresenter.class);
CountryKnowledgeContainer countryKnowledgeContainer;
public FxKnowledgePresenter(final CountryKnowledgeContainer countryKnowledgeContainer) {
this.countryKnowledgeContainer = countryKnowledgeContainer;
fillAccordion();
}
private void fillAccordion() {
this.getPanes().clear();
for (Category category : Category.values()) {
ResourceBundle messageBundle = SingletonConfiguration.getInstance().getMessageBundle();
TitledPane titledPane = new TitledPane();
titledPane.setText(messageBundle.getString("categories."+category.name()));
List<String> knowledgeList = countryKnowledgeContainer.getKnowledge(category);
VBox box = new VBox();
for (String string : knowledgeList) {
box.getChildren().add(new Label(string));
logger.info("Adding label " + string);
}
titledPane.setContent(box);
this.getPanes().add(titledPane);
}
}
public void refresh() {
fillAccordion();
}
}

View File

@@ -0,0 +1,59 @@
package prog.ex15.solution.i18ncountries.gui;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import prog.ex15.exercise.i18ncountries.Country;
import prog.ex15.exercise.i18ncountries.CountryKnowledgeContainer;
import prog.ex15.solution.i18ncountries.I18nKnowledgeGenerator;
import prog.ex15.solution.i18ncountries.SingletonConfiguration;
import java.util.Locale;
/**
* Main to launch the WelcomeToMyCountry content in a separate application.
*/
public class MultilingualWelcomeLauncher extends Application {
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(MultilingualWelcomeLauncher.class);
@Override
public void start(final Stage stage) throws Exception {
logger.debug("start: {}", stage);
SingletonConfiguration.getInstance().setLocale(new Locale("en", "GB"));
CountryKnowledgeContainer container = new CountryKnowledgeContainer();
I18nKnowledgeGenerator generator = new I18nKnowledgeGenerator();
generator.fillContainer(container);
FxKnowledgePresenter presenter = new FxKnowledgePresenter(container);
ComboBox<Country> countrySelector = new ComboBox<>();
countrySelector.getItems().addAll(Country.values());
countrySelector.setValue(Country.ENGLAND);
countrySelector.setOnAction(e -> {
Country selectedCountry = countrySelector.getValue();
Locale newLocale = SingletonConfiguration.getInstance()
.getCountry2LocaleMap()
.get(selectedCountry);
SingletonConfiguration.getInstance().setLocale(newLocale);
generator.fillContainer(container);
});
SingletonConfiguration.getInstance().addPropertyChangeListener(evt -> {
if ("locale".equals(evt.getPropertyName())) {
generator.fillContainer(container);
presenter.refresh();
}
});
VBox root = new VBox(10);
root.getChildren().addAll(countrySelector, presenter);
stage.setScene(new Scene(root, 400, 300));
stage.show();
}
}