1. Domain

package org.jminor.framework.demos.world.domain;

import org.jminor.common.item.Item;
import org.jminor.framework.domain.Domain;
import org.jminor.framework.domain.entity.ColorProvider;
import org.jminor.framework.domain.entity.DefaultEntityValidator;
import org.jminor.framework.domain.entity.Entity;
import org.jminor.framework.domain.entity.EntityDefinition;
import org.jminor.framework.domain.entity.StringProvider;
import org.jminor.framework.domain.entity.exception.ValidationException;
import org.jminor.framework.domain.property.DerivedProperty;
import org.jminor.framework.domain.property.Property;

import java.awt.Color;
import java.sql.Types;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static java.util.Arrays.asList;
import static org.jminor.common.Util.notNull;
import static org.jminor.common.item.Items.item;
import static org.jminor.framework.domain.entity.KeyGenerators.sequence;
import static org.jminor.framework.domain.entity.OrderBy.orderBy;
import static org.jminor.framework.domain.property.Properties.*;

public final class World extends Domain {

  public static final String T_CITY = "world.city";
  public static final String CITY_ID = "id";
  public static final String CITY_NAME = "name";
  public static final String CITY_COUNTRY_CODE = "countrycode";
  public static final String CITY_COUNTRY_FK = "country_fk";
  public static final String CITY_DISTRICT = "district";
  public static final String CITY_POPULATION = "population";

  public static final String T_COUNTRY = "world.country";
  public static final String COUNTRY_CODE = "code";
  public static final String COUNTRY_NAME = "name";
  public static final String COUNTRY_CONTINENT = "continent";
  public static final String COUNTRY_REGION = "region";
  public static final String COUNTRY_SURFACEAREA = "surfacearea";
  public static final String COUNTRY_INDEPYEAR = "indepyear";
  public static final String COUNTRY_POPULATION = "population";
  public static final String COUNTRY_LIFEEXPECTANCY = "lifeexpectancy";
  public static final String COUNTRY_GNP = "gnp";
  public static final String COUNTRY_GNPOLD = "gnpold";
  public static final String COUNTRY_LOCALNAME = "localname";
  public static final String COUNTRY_GOVERNMENTFORM = "governmentform";
  public static final String COUNTRY_HEADOFSTATE = "headofstate";
  public static final String COUNTRY_CAPITAL = "capital";
  public static final String COUNTRY_CAPITAL_FK = "capital_fk";
  public static final String COUNTRY_CODE2 = "code2";
  public static final String COUNTRY_CAPITAL_POPULATION = "capital_population";
  public static final String COUNTRY_NO_OF_CITIES = "no_of_cities";
  public static final String COUNTRY_NO_OF_LANGUAGES = "no_of_languages";
  public static final String COUNTRY_FLAG = "flag";

  public static final String T_COUNTRYLANGUAGE = "world.countrylanguage";
  public static final String COUNTRYLANGUAGE_COUNTRY_CODE = "countrycode";
  public static final String COUNTRYLANGUAGE_COUNTRY_FK = "country_fk";
  public static final String COUNTRYLANGUAGE_LANGUAGE = "language";
  public static final String COUNTRYLANGUAGE_ISOFFICIAL = "isofficial";
  public static final String COUNTRYLANGUAGE_PERCENTAGE = "percentage";
  public static final String COUNTRYLANGUAGE_NO_OF_SPEAKERS = "no_of_speakers";

  public static final String T_CONTINENT = "continent";
  public static final String CONTINENT_CONTINENT = "continent";
  public static final String CONTINENT_SURFACE_AREA = "sum(surfacearea)";
  public static final String CONTINENT_POPULATION = "sum(population)";
  public static final String CONTINENT_MIN_LIFE_EXPECTANCY = "min(lifeexpectancy)";
  public static final String CONTINENT_MAX_LIFE_EXPECTANCY = "max(lifeexpectancy)";
  public static final String CONTINENT_MIN_INDEPENDENCE_YEAR = "min(indepyear)";
  public static final String CONTINENT_MAX_INDEPENDENCE_YEAR = "max(indepyear)";
  public static final String CONTINENT_GNP = "sum(gnp)";

  public static final String T_LOOKUP = "world.country_city_v";
  public static final String LOOKUP_COUNTRY_CODE = "countrycode";
  public static final String LOOKUP_COUNTRY_NAME = "countryname";
  public static final String LOOKUP_COUNTRY_CONTINENT = "continent";
  public static final String LOOKUP_COUNTRY_REGION = "region";
  public static final String LOOKUP_COUNTRY_SURFACEAREA = "surfacearea";
  public static final String LOOKUP_COUNTRY_INDEPYEAR = "indepyear";
  public static final String LOOKUP_COUNTRY_POPULATION = "countrypopulation";
  public static final String LOOKUP_COUNTRY_LIFEEXPECTANCY = "lifeexpectancy";
  public static final String LOOKUP_COUNTRY_GNP = "gnp";
  public static final String LOOKUP_COUNTRY_GNPOLD = "gnpold";
  public static final String LOOKUP_COUNTRY_LOCALNAME = "localname";
  public static final String LOOKUP_COUNTRY_GOVERNMENTFORM = "governmentform";
  public static final String LOOKUP_COUNTRY_HEADOFSTATE = "headofstate";
  public static final String LOOKUP_COUNTRY_CODE2 = "code2";
  public static final String LOOKUP_COUNTRY_FLAG = "flag";
  public static final String LOOKUP_CITY_ID = "cityid";
  public static final String LOOKUP_CITY_NAME = "cityname";
  public static final String LOOKUP_CITY_DISTRICT = "district";
  public static final String LOOKUP_CITY_POPULATION = "citypopulation";

  private static final List<Item> CONTINENTS = asList(
          item("Africa"), item("Antarctica"), item("Asia"),
          item("Europe"), item("North America"), item("Oceania"),
          item("South America")
  );

  public World() {
    //disable this default check so we can define a foreign key relation
    //from city to country without having defined the country entity
    setStrictForeignKeys(false);

    city();
    country();
    countryLanguage();
    lookup();
    continent();
  }

  void city() {
    define(T_CITY,
            primaryKeyProperty(CITY_ID),
            columnProperty(CITY_NAME, Types.VARCHAR, "Name")
                    .nullable(false)
                    .maximumLength(35),
            foreignKeyProperty(CITY_COUNTRY_FK, "Country", T_COUNTRY,
                    columnProperty(CITY_COUNTRY_CODE, Types.VARCHAR))
                    .nullable(false),
            columnProperty(CITY_DISTRICT, Types.VARCHAR, "District")
                    .nullable(false)
                    .maximumLength(20),
            columnProperty(CITY_POPULATION, Types.INTEGER, "Population")
                    .nullable(false)
                    .useNumberFormatGrouping(true))
            .keyGenerator(sequence("world.city_seq"))
            .validator(new CityValidator())
            .orderBy(orderBy().ascending(CITY_NAME))
            .searchPropertyIds(CITY_NAME)
            .stringProvider(new StringProvider(CITY_NAME))
            .colorProvider(new CityColorProvider())
            .caption("City");
  }

  void country() {
    define(T_COUNTRY,
            primaryKeyProperty(COUNTRY_CODE, Types.VARCHAR, "Country code")
                    .updatable(true)
                    .maximumLength(3),
            columnProperty(COUNTRY_NAME, Types.VARCHAR, "Name")
                    .nullable(false)
                    .maximumLength(52),
            valueListProperty(COUNTRY_CONTINENT, Types.VARCHAR, "Continent", CONTINENTS)
                    .nullable(false)
                    .maximumLength(20),
            columnProperty(COUNTRY_REGION, Types.VARCHAR, "Region")
                    .nullable(false)
                    .maximumLength(26),
            columnProperty(COUNTRY_SURFACEAREA, Types.DOUBLE, "Surface area")
                    .nullable(false)
                    .useNumberFormatGrouping(true)
                    .maximumFractionDigits(2),
            columnProperty(COUNTRY_INDEPYEAR, Types.INTEGER, "Indep. year")
                    .minimumValue(-2000).maximumValue(2500),
            columnProperty(COUNTRY_POPULATION, Types.INTEGER, "Population")
                    .nullable(false)
                    .useNumberFormatGrouping(true),
            columnProperty(COUNTRY_LIFEEXPECTANCY, Types.DOUBLE, "Life expectancy")
                    .maximumFractionDigits(1)
                    .minimumValue(0).maximumValue(99),
            columnProperty(COUNTRY_GNP, Types.DOUBLE, "GNP")
                    .useNumberFormatGrouping(true)
                    .maximumFractionDigits(2),
            columnProperty(COUNTRY_GNPOLD, Types.DOUBLE, "GNP old")
                    .useNumberFormatGrouping(true)
                    .maximumFractionDigits(2),
            columnProperty(COUNTRY_LOCALNAME, Types.VARCHAR, "Local name")
                    .nullable(false)
                    .maximumLength(45),
            columnProperty(COUNTRY_GOVERNMENTFORM, Types.VARCHAR, "Government form")
                    .nullable(false),
            columnProperty(COUNTRY_HEADOFSTATE, Types.VARCHAR, "Head of state")
                    .maximumLength(60),
            foreignKeyProperty(COUNTRY_CAPITAL_FK, "Capital", T_CITY,
                    columnProperty(COUNTRY_CAPITAL)),
            denormalizedViewProperty(COUNTRY_CAPITAL_POPULATION, COUNTRY_CAPITAL_FK,
                    getDefinition(T_CITY).getProperty(CITY_POPULATION), "Capital pop.")
                    .useNumberFormatGrouping(true),
            subqueryProperty(COUNTRY_NO_OF_CITIES, Types.INTEGER, "No. of cities",
                    "select count(*) from world.city where countrycode = code"),
            subqueryProperty(COUNTRY_NO_OF_LANGUAGES, Types.INTEGER, "No. of languages",
                    "select count(*) from world.countrylanguage where countrycode = code"),
            blobProperty(COUNTRY_FLAG, "Flag")
                    .eagerlyLoaded(true),
            columnProperty(COUNTRY_CODE2, Types.VARCHAR, "Code2")
                    .nullable(false)
                    .maximumLength(2))
            .orderBy(orderBy().ascending(COUNTRY_NAME))
            .searchPropertyIds(COUNTRY_NAME)
            .stringProvider(new StringProvider(COUNTRY_NAME))
            .caption("Country");
  }

  void countryLanguage() {
    define(T_COUNTRYLANGUAGE,
            foreignKeyProperty(COUNTRYLANGUAGE_COUNTRY_FK, "Country", T_COUNTRY,
                    columnProperty(COUNTRYLANGUAGE_COUNTRY_CODE, Types.VARCHAR)
                            .primaryKeyIndex(0)
                            .updatable(true))
                    .nullable(false),
            columnProperty(COUNTRYLANGUAGE_LANGUAGE, Types.VARCHAR, "Language")
                    .primaryKeyIndex(1)
                    .updatable(true),
            columnProperty(COUNTRYLANGUAGE_ISOFFICIAL, Types.BOOLEAN, "Is official")
                    .columnHasDefaultValue(true)
                    .nullable(false),
            columnProperty(COUNTRYLANGUAGE_PERCENTAGE, Types.DOUBLE, "Percentage")
                    .nullable(false)
                    .maximumFractionDigits(1)
                    .minimumValue(0).maximumValue(100),
            derivedProperty(COUNTRYLANGUAGE_NO_OF_SPEAKERS, Types.INTEGER, "No. of speakers",
                    new NoOfSpeakersProvider(), COUNTRYLANGUAGE_COUNTRY_FK, COUNTRYLANGUAGE_PERCENTAGE)
                    .useNumberFormatGrouping(true)
    ).orderBy(orderBy().ascending(COUNTRYLANGUAGE_LANGUAGE).descending(COUNTRYLANGUAGE_PERCENTAGE))
            .caption("Language");
  }

  void lookup() {
    define(T_LOOKUP,
            columnProperty(LOOKUP_COUNTRY_CODE, Types.VARCHAR, "Country code"),
            columnProperty(LOOKUP_COUNTRY_NAME, Types.VARCHAR, "Country name"),
            columnProperty(LOOKUP_COUNTRY_CONTINENT, Types.VARCHAR, "Continent"),
            columnProperty(LOOKUP_COUNTRY_REGION, Types.VARCHAR, "Region"),
            columnProperty(LOOKUP_COUNTRY_SURFACEAREA, Types.DOUBLE, "Surface area")
                    .useNumberFormatGrouping(true),
            columnProperty(LOOKUP_COUNTRY_INDEPYEAR, Types.INTEGER, "Indep. year"),
            columnProperty(LOOKUP_COUNTRY_POPULATION, Types.INTEGER, "Country population")
                    .useNumberFormatGrouping(true),
            columnProperty(LOOKUP_COUNTRY_LIFEEXPECTANCY, Types.DOUBLE, "Life expectancy"),
            columnProperty(LOOKUP_COUNTRY_GNP, Types.DOUBLE, "GNP")
                    .useNumberFormatGrouping(true),
            columnProperty(LOOKUP_COUNTRY_GNPOLD, Types.DOUBLE, "GNP old")
                    .useNumberFormatGrouping(true),
            columnProperty(LOOKUP_COUNTRY_LOCALNAME, Types.VARCHAR, "Local name"),
            columnProperty(LOOKUP_COUNTRY_GOVERNMENTFORM, Types.VARCHAR, "Government form"),
            columnProperty(LOOKUP_COUNTRY_HEADOFSTATE, Types.VARCHAR, "Head of state"),
            blobProperty(LOOKUP_COUNTRY_FLAG, "Flag"),
            columnProperty(LOOKUP_COUNTRY_CODE2, Types.VARCHAR, "Code2"),
            columnProperty(LOOKUP_CITY_ID),
            columnProperty(LOOKUP_CITY_NAME, Types.VARCHAR, "Name"),
            columnProperty(LOOKUP_CITY_DISTRICT, Types.VARCHAR, "District"),
            columnProperty(LOOKUP_CITY_POPULATION, Types.INTEGER, "City population")
                    .useNumberFormatGrouping(true))
            .orderBy(orderBy().ascending(LOOKUP_COUNTRY_NAME).descending(LOOKUP_CITY_POPULATION))
            .readOnly(true)
            .caption("Lookup");
  }

  void continent() {
    define(T_CONTINENT, "world.country",
            columnProperty(CONTINENT_CONTINENT, Types.VARCHAR, "Continent")
                    .groupingColumn(true),
            columnProperty(CONTINENT_SURFACE_AREA, Types.INTEGER, "Surface area")
                    .aggregateColumn(true)
                    .useNumberFormatGrouping(true),
            columnProperty(CONTINENT_POPULATION, Types.BIGINT, "Population")
                    .aggregateColumn(true)
                    .useNumberFormatGrouping(true),
            columnProperty(CONTINENT_MIN_LIFE_EXPECTANCY, Types.DOUBLE, "Min. life expectancy")
                    .aggregateColumn(true),
            columnProperty(CONTINENT_MAX_LIFE_EXPECTANCY, Types.DOUBLE, "Max. life expectancy")
                    .aggregateColumn(true),
            columnProperty(CONTINENT_MIN_INDEPENDENCE_YEAR, Types.INTEGER, "Min. ind. year")
                    .aggregateColumn(true),
            columnProperty(CONTINENT_MAX_INDEPENDENCE_YEAR, Types.INTEGER, "Max. ind. year")
                    .aggregateColumn(true),
            columnProperty(CONTINENT_GNP, Types.DOUBLE, "GNP")
                    .aggregateColumn(true)
                    .useNumberFormatGrouping(true))
            .readOnly(true)
            .caption("Continent");
  }

  private static final class CityColorProvider implements ColorProvider {

    private static final long serialVersionUID = 1;

    @Override
    public Object getColor(Entity city, Property property) {
      if (property.is(CITY_POPULATION) &&
              city.getInteger(CITY_POPULATION) > 1_000_000) {
        //population YELLOW if > 1.000.000
        return Color.YELLOW;
      }
      if (property.is(CITY_NAME) &&
              Objects.equals(city.get(World.CITY_ID),
                      city.getForeignKey(World.CITY_COUNTRY_FK).get(World.COUNTRY_CAPITAL))) {
        //name CYAN if capital city
        return Color.CYAN;
      }

      return null;
    }
  }

  private static final class NoOfSpeakersProvider implements DerivedProperty.Provider {

    private static final long serialVersionUID = 1;

    @Override
    public Object getValue(Map<String, Object> sourceValues) {
      Double percentage = (Double) sourceValues.get(COUNTRYLANGUAGE_PERCENTAGE);
      Entity country = (Entity) sourceValues.get(COUNTRYLANGUAGE_COUNTRY_FK);
      if (notNull(percentage, country) && country.isNotNull(COUNTRY_POPULATION)) {
        return Double.valueOf(country.getInteger(COUNTRY_POPULATION) * (percentage / 100)).intValue();
      }

      return null;
    }
  }

  private static final class CityValidator extends DefaultEntityValidator {

    private static final long serialVersionUID = 1;

    @Override
    public void validate(Entity city, EntityDefinition cityDefinition) throws ValidationException {
      super.validate(city, cityDefinition);
      //after a call to super.validate() property values that are not nullable
      //(such as country and population) are guaranteed to be non-null
      Entity country = city.getForeignKey(CITY_COUNTRY_FK);
      Integer cityPopulation = city.getInteger(CITY_POPULATION);
      Integer countryPopulation = country.getInteger(COUNTRY_POPULATION);
      if (countryPopulation != null && cityPopulation > countryPopulation) {
        throw new ValidationException(CITY_POPULATION,
                cityPopulation, "City population can not exceed country population");
      }
    }
  }
}

1.1. Domain unit test

package org.jminor.framework.demos.world.domain;

import org.jminor.common.db.exception.DatabaseException;
import org.jminor.framework.domain.entity.Entity;
import org.jminor.framework.domain.entity.test.EntityTestUnit;

import org.junit.jupiter.api.Test;

import java.util.Map;

public final class WorldTest extends EntityTestUnit {

  public WorldTest() {
    super(World.class.getName());
  }

  @Test
  public void country() throws DatabaseException {
    test(World.T_COUNTRY);
  }

  @Test
  public void city() throws DatabaseException {
    test(World.T_CITY);
  }

  @Test
  public void countryLanguage() throws DatabaseException {
    test(World.T_COUNTRYLANGUAGE);
  }

  @Override
  protected Entity initializeTestEntity(String entityId, Map<String, Entity> foreignKeyEntities) {
    Entity entity = super.initializeTestEntity(entityId, foreignKeyEntities);
    if (entityId.equals(World.T_COUNTRY)) {
      entity.put(World.COUNTRY_CODE, "XXX");
      entity.put(World.COUNTRY_CONTINENT, "Asia");
    }

    return entity;
  }

  @Override
  protected void modifyEntity(Entity testEntity, Map<String, Entity> foreignKeyEntities) {
    super.modifyEntity(testEntity, foreignKeyEntities);
    if (testEntity.is(World.T_COUNTRY)) {
      testEntity.put(World.COUNTRY_CONTINENT, "Europe");
    }
  }

  @Override
  protected Entity initializeReferenceEntity(String entityId, Map<String, Entity> foreignKeyEntities) {
    switch (entityId) {
      case World.T_COUNTRY:
        Entity iceland = getDomain().entity(World.T_COUNTRY);
        iceland.put(World.COUNTRY_CODE, "ISL");

        return iceland;
      case World.T_CITY:
        Entity reykjavik = getDomain().entity(World.T_CITY);
        reykjavik.put(World.CITY_ID, 1449);

        return reykjavik;
    }

    return super.initializeReferenceEntity(entityId, foreignKeyEntities);
  }
}

2. Model

package org.jminor.framework.demos.world.model;

import org.jminor.framework.db.EntityConnectionProvider;
import org.jminor.framework.demos.world.domain.World;
import org.jminor.framework.domain.property.ForeignKeyProperty;
import org.jminor.swing.framework.model.SwingEntityComboBoxModel;
import org.jminor.swing.framework.model.SwingEntityEditModel;

import java.util.Objects;

public final class CountryEditModel extends SwingEntityEditModel {

  public CountryEditModel(EntityConnectionProvider connectionProvider) {
    super(World.T_COUNTRY, connectionProvider);
  }

  @Override
  public SwingEntityComboBoxModel createForeignKeyComboBoxModel(ForeignKeyProperty foreignKeyProperty) {
    SwingEntityComboBoxModel comboBoxModel = super.createForeignKeyComboBoxModel(foreignKeyProperty);
    if (foreignKeyProperty.is(World.COUNTRY_CAPITAL_FK)) {
      //only show cities for currently selected country
      addEntitySetListener(selectedCountry -> comboBoxModel.setIncludeCondition(
              city -> selectedCountry != null &&
                      Objects.equals(selectedCountry.get(World.COUNTRY_CODE),
                              city.get(World.CITY_COUNTRY_CODE))));
    }

    return comboBoxModel;
  }
}
package org.jminor.framework.demos.world.model;

import org.jminor.common.db.exception.DatabaseException;
import org.jminor.common.model.table.ColumnConditionModel;
import org.jminor.common.model.table.ColumnConditionModel.AutomaticWildcard;
import org.jminor.framework.db.EntityConnection;
import org.jminor.framework.db.EntityConnectionProvider;
import org.jminor.framework.demos.world.domain.World;
import org.jminor.framework.domain.entity.Entity;
import org.jminor.swing.framework.model.SwingEntityTableModel;

import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;

import java.util.List;

public final class CountryTableModel extends SwingEntityTableModel {

  private final DefaultPieDataset citiesDataset = new DefaultPieDataset();
  private final DefaultPieDataset languagesDataset = new DefaultPieDataset();

  public CountryTableModel(EntityConnectionProvider connectionProvider) {
    super(World.T_COUNTRY, connectionProvider);
    configureConditionModels();
    bindEvents();
  }

  public PieDataset getCitiesDataset() {
    return citiesDataset;
  }

  public PieDataset getLanguagesDataset() {
    return languagesDataset;
  }

  private void configureConditionModels() {
    getConditionModel().getPropertyConditionModels().stream().filter(model ->
            model.getColumnIdentifier().isString()).forEach(this::configureConditionModel);
  }

  private void configureConditionModel(ColumnConditionModel model) {
    model.setCaseSensitive(false);
    model.setAutomaticWildcard(AutomaticWildcard.PREFIX_AND_POSTFIX);
  }

  private void bindEvents() {
    getSelectionModel().addSelectedItemsListener(this::refreshChartDatasets);
  }

  private void refreshChartDatasets(List<Entity> selectedCountries) {
    citiesDataset.clear();
    languagesDataset.clear();
    try {
      if (!selectedCountries.isEmpty()) {
        final EntityConnection connection = getConnectionProvider().getConnection();

        List<Entity> cities = connection.select(World.T_CITY,
                World.CITY_COUNTRY_FK, selectedCountries);
        cities.forEach(city -> citiesDataset.setValue(
                city.getString(World.CITY_NAME),
                city.getInteger(World.CITY_POPULATION)));

        List<Entity> languages = connection.select(World.T_COUNTRYLANGUAGE,
                World.COUNTRYLANGUAGE_COUNTRY_FK, selectedCountries);
        languages.forEach(language -> languagesDataset.setValue(
                language.getString(World.COUNTRYLANGUAGE_LANGUAGE),
                language.getInteger(World.COUNTRYLANGUAGE_NO_OF_SPEAKERS)));
      }
    }
    catch (DatabaseException e) {
      throw new RuntimeException(e);
    }
  }
}
package org.jminor.framework.demos.world.model;

import org.jminor.framework.db.EntityConnectionProvider;
import org.jminor.framework.demos.world.domain.World;
import org.jminor.swing.framework.model.SwingEntityModel;

public final class CountryModel extends SwingEntityModel {

  public CountryModel(final EntityConnectionProvider connectionProvider) {
    super(new CountryEditModel(connectionProvider), new CountryTableModel(connectionProvider));
    SwingEntityModel cityModel = new SwingEntityModel(World.T_CITY, connectionProvider);
    SwingEntityModel countryLanguageModel = new SwingEntityModel(World.T_COUNTRYLANGUAGE, connectionProvider);
    addDetailModels(cityModel, countryLanguageModel);
  }
}
package org.jminor.framework.demos.world.model;

import org.jminor.framework.db.EntityConnectionProvider;
import org.jminor.framework.demos.world.domain.World;
import org.jminor.swing.framework.model.SwingEntityModel;

public final class CountryCustomModel extends SwingEntityModel {

  public CountryCustomModel(final EntityConnectionProvider connectionProvider) {
    super(new CountryEditModel(connectionProvider), new CountryTableModel(connectionProvider));
    SwingEntityModel cityModel = new SwingEntityModel(World.T_CITY, connectionProvider);
    SwingEntityModel countryLanguageModel = new SwingEntityModel(World.T_COUNTRYLANGUAGE, connectionProvider);
    addDetailModels(cityModel, countryLanguageModel);
  }
}
package org.jminor.framework.demos.world.model;

import org.jminor.framework.db.EntityConnectionProvider;
import org.jminor.framework.demos.world.domain.World;
import org.jminor.swing.framework.model.SwingEntityModel;

import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;

public final class ContinentModel extends SwingEntityModel {

  private final DefaultPieDataset surfaceAreaDataset = new DefaultPieDataset();
  private final DefaultPieDataset populationDataset = new DefaultPieDataset();
  private final DefaultPieDataset gnpDataset = new DefaultPieDataset();
  private final DefaultCategoryDataset lifeExpectancyDataset = new DefaultCategoryDataset();

  public ContinentModel(EntityConnectionProvider connectionProvider) {
    super(World.T_CONTINENT, connectionProvider);
    getTableModel().addRefreshListener(this::refreshChartDatasets);
  }

  public PieDataset getPopulationDataset() {
    return populationDataset;
  }

  public PieDataset getSurfaceAreaDataset() {
    return surfaceAreaDataset;
  }

  public PieDataset getGnpDataset() {
    return gnpDataset;
  }

  public CategoryDataset getLifeExpectancyDataset() {
    return lifeExpectancyDataset;
  }

  private void refreshChartDatasets() {
    populationDataset.clear();
    surfaceAreaDataset.clear();
    gnpDataset.clear();
    lifeExpectancyDataset.clear();
    getTableModel().getItems().forEach(continent -> {
      populationDataset.setValue(
            continent.getString(World.CONTINENT_CONTINENT),
            continent.getLong(World.CONTINENT_POPULATION));
      surfaceAreaDataset.setValue(
            continent.getString(World.CONTINENT_CONTINENT),
            continent.getInteger(World.CONTINENT_SURFACE_AREA));
      gnpDataset.setValue(
            continent.getString(World.CONTINENT_CONTINENT),
            continent.getDouble(World.CONTINENT_GNP));
      lifeExpectancyDataset.addValue(
              continent.getDouble(World.CONTINENT_MIN_LIFE_EXPECTANCY),
              "Minimum", continent.getString(World.CONTINENT_CONTINENT));
      lifeExpectancyDataset.addValue(
              continent.getDouble(World.CONTINENT_MAX_LIFE_EXPECTANCY),
              "Maximum",continent.getString(World.CONTINENT_CONTINENT));
    });
  }
}
package org.jminor.framework.demos.world.model;

import org.jminor.common.model.table.ColumnConditionModel;
import org.jminor.common.model.table.ColumnConditionModel.AutomaticWildcard;
import org.jminor.framework.db.EntityConnectionProvider;
import org.jminor.framework.demos.world.domain.World;
import org.jminor.swing.framework.model.SwingEntityTableModel;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

import static java.util.Collections.singletonList;

public final class LookupTableModel extends SwingEntityTableModel {

  public LookupTableModel(EntityConnectionProvider connectionProvider) {
    super(World.T_LOOKUP, connectionProvider);
    configureConditionModels();
  }

  public void exportCSV(File file) throws IOException {
    Files.write(file.toPath(), singletonList(getTableDataAsDelimitedString(',')));
  }

  private void configureConditionModels() {
    getConditionModel().getPropertyConditionModels().stream().filter(model ->
            model.getColumnIdentifier().isString()).forEach(this::configureConditionModel);
  }

  private void configureConditionModel(ColumnConditionModel model) {
    model.setCaseSensitive(false);
    model.setAutomaticWildcard(AutomaticWildcard.PREFIX_AND_POSTFIX);
  }
}

2.1. Main application model

package org.jminor.framework.demos.world.model;

import org.jminor.framework.db.EntityConnectionProvider;
import org.jminor.swing.framework.model.SwingEntityApplicationModel;
import org.jminor.swing.framework.model.SwingEntityModel;

public final class WorldAppModel extends SwingEntityApplicationModel {

  public WorldAppModel(EntityConnectionProvider connectionProvider) {
    super(connectionProvider);
    setupEntityModels(connectionProvider);
  }

  private void setupEntityModels(EntityConnectionProvider connectionProvider) {
    SwingEntityModel countryModel = new CountryModel(connectionProvider);
    SwingEntityModel customCountryModel = new CountryCustomModel(connectionProvider);
    SwingEntityModel lookupModel = new SwingEntityModel(new LookupTableModel(connectionProvider));
    SwingEntityModel continentModel = new ContinentModel(connectionProvider);

    addEntityModels(countryModel, lookupModel, continentModel);
  }
}

3. UI

package org.jminor.framework.demos.world.ui;

import org.jminor.framework.demos.world.domain.World;
import org.jminor.framework.demos.world.model.CountryEditModel;
import org.jminor.framework.domain.entity.Entity;
import org.jminor.swing.framework.ui.EntityComboBox;
import org.jminor.swing.framework.ui.EntityEditPanel;
import org.jminor.swing.framework.ui.EntityPanelBuilder;

import javax.swing.JComponent;
import javax.swing.JPanel;

import static org.jminor.swing.common.ui.Components.createEastButtonPanel;
import static org.jminor.swing.common.ui.Components.setPreferredWidth;
import static org.jminor.swing.common.ui.layout.Layouts.gridLayout;
import static org.jminor.swing.common.ui.textfield.TextFields.makeUpperCase;

public final class CountryEditPanel extends EntityEditPanel {

  public CountryEditPanel(CountryEditModel editModel) {
    super(editModel);
  }

  @Override
  protected void initializeUI() {
    setInitialFocusProperty(World.COUNTRY_CODE);

    makeUpperCase(createTextField(World.COUNTRY_CODE)).setColumns(12);
    makeUpperCase(createTextField(World.COUNTRY_CODE2)).setColumns(12);
    createTextField(World.COUNTRY_NAME).setColumns(12);
    setPreferredWidth(createValueListComboBox(World.COUNTRY_CONTINENT), 120);
    setPreferredWidth(createPropertyComboBox(World.COUNTRY_REGION), 120);
    createTextField(World.COUNTRY_SURFACEAREA);
    createTextField(World.COUNTRY_INDEPYEAR);
    createTextField(World.COUNTRY_POPULATION);
    createTextField(World.COUNTRY_LIFEEXPECTANCY);
    createTextField(World.COUNTRY_GNP);
    createTextField(World.COUNTRY_GNPOLD);
    createTextField(World.COUNTRY_LOCALNAME).setColumns(12);
    setPreferredWidth(createPropertyComboBox(World.COUNTRY_GOVERNMENTFORM, null, true), 120);
    createTextField(World.COUNTRY_HEADOFSTATE).setColumns(12);
    EntityComboBox capitalComboBox =
            setPreferredWidth(createForeignKeyComboBox(World.COUNTRY_CAPITAL_FK), 120);
    //create a panel with a button for adding a new city
    JPanel capitalPanel = createEastButtonPanel(capitalComboBox,
            createEditPanelAction(capitalComboBox, new CityPanelBuilder()), false);

    setLayout(gridLayout(4, 5));

    addPropertyPanel(World.COUNTRY_CODE);
    addPropertyPanel(World.COUNTRY_CODE2);
    addPropertyPanel(World.COUNTRY_NAME);
    addPropertyPanel(World.COUNTRY_CONTINENT);
    addPropertyPanel(World.COUNTRY_REGION);
    addPropertyPanel(World.COUNTRY_SURFACEAREA);
    addPropertyPanel(World.COUNTRY_INDEPYEAR);
    addPropertyPanel(World.COUNTRY_POPULATION);
    addPropertyPanel(World.COUNTRY_LIFEEXPECTANCY);
    addPropertyPanel(World.COUNTRY_GNP);
    addPropertyPanel(World.COUNTRY_GNPOLD);
    addPropertyPanel(World.COUNTRY_LOCALNAME);
    addPropertyPanel(World.COUNTRY_GOVERNMENTFORM);
    addPropertyPanel(World.COUNTRY_HEADOFSTATE);
    add(createPropertyPanel(World.COUNTRY_CAPITAL_FK, capitalPanel));
  }

  /** A EntityPanelBuilder for adding a new city */
  private final class CityPanelBuilder extends EntityPanelBuilder {

    public CityPanelBuilder() {
      super(World.T_CITY);
      setEditPanelClass(CityEditPanel.class);
    }

    @Override
    protected void configureEditPanel(EntityEditPanel editPanel) {
      //set the country to the one selected in the CountryEditPanel
      Entity country = CountryEditPanel.this.getEditModel().getEntityCopy();
      if (country.getKey().isNotNull()) {
        //if a country is selected, then we don't allow it to be changed
        editPanel.getEditModel().put(World.CITY_COUNTRY_FK, country);
        //initialize the panel components, so we can configure the country component
        editPanel.initializePanel();
        //disable the country selection component
        JComponent countryComponent = editPanel.getComponent(World.CITY_COUNTRY_FK);
        countryComponent.setEnabled(false);
        countryComponent.setFocusable(false);
        //and change the initial focus property
        editPanel.setInitialFocusProperty(World.CITY_NAME);
      }
    }
  }
}
package org.jminor.framework.demos.world.ui;

import org.jminor.common.db.exception.DatabaseException;
import org.jminor.framework.demos.world.model.CountryTableModel;
import org.jminor.swing.common.ui.control.Controls;
import org.jminor.swing.common.ui.dialog.Dialogs;
import org.jminor.swing.common.ui.dialog.Modal;
import org.jminor.swing.framework.ui.EntityTablePanel;

import org.jfree.chart.ChartPanel;

import javax.swing.JTabbedPane;

import static org.jfree.chart.ChartFactory.createPieChart;

public final class CountryTablePanel extends EntityTablePanel {

  private final JTabbedPane pieChartPane = new JTabbedPane();
  private final ChartPanel cityChartPanel;
  private final ChartPanel languageChartPanel;

  public CountryTablePanel(CountryTableModel tableModel) {
    super(tableModel);
    cityChartPanel = new ChartPanel(createPieChart("Cities", tableModel.getCitiesDataset()));
    cityChartPanel.getChart().removeLegend();
    languageChartPanel = new ChartPanel(createPieChart("Languages", tableModel.getLanguagesDataset()));
    languageChartPanel.getChart().removeLegend();
    pieChartPane.addTab("Cities", cityChartPanel);
    pieChartPane.addTab("Languages", languageChartPanel);
    getTable().setDoubleClickAction(Controls.control(this::displayChartPanel,
            tableModel.getSelectionModel().getSelectionNotEmptyObserver()));
  }

  public ChartPanel getCityChartPanel() {
    return cityChartPanel;
  }

  public ChartPanel getLanguageChartPanel() {
    return languageChartPanel;
  }

  private void displayChartPanel() throws DatabaseException {
    if (!pieChartPane.isShowing()) {
      Dialogs.displayInDialog(this, pieChartPane, Modal.NO);
    }
  }
}
package org.jminor.framework.demos.world.ui;

import org.jminor.framework.demos.world.domain.World;
import org.jminor.swing.framework.model.SwingEntityEditModel;
import org.jminor.swing.framework.ui.EntityEditPanel;

import static org.jminor.swing.common.ui.Components.setPreferredWidth;
import static org.jminor.swing.common.ui.layout.Layouts.gridLayout;

public final class CityEditPanel extends EntityEditPanel {

  public CityEditPanel(SwingEntityEditModel editModel) {
    super(editModel);
  }

  @Override
  protected void initializeUI() {
    setInitialFocusProperty(World.CITY_COUNTRY_FK);

    setPreferredWidth(createForeignKeyComboBox(World.CITY_COUNTRY_FK), 120);
    createTextField(World.CITY_NAME).setColumns(12);
    createTextField(World.CITY_DISTRICT).setColumns(12);
    createTextField(World.CITY_POPULATION);

    setLayout(gridLayout(2, 2));

    addPropertyPanel(World.CITY_COUNTRY_FK);
    addPropertyPanel(World.CITY_NAME);
    addPropertyPanel(World.CITY_DISTRICT);
    addPropertyPanel(World.CITY_POPULATION);
  }
}
package org.jminor.framework.demos.world.ui;

import org.jminor.framework.demos.world.domain.World;
import org.jminor.swing.framework.model.SwingEntityEditModel;
import org.jminor.swing.framework.ui.EntityEditPanel;

import static org.jminor.swing.common.ui.Components.setPreferredWidth;
import static org.jminor.swing.common.ui.layout.Layouts.gridLayout;

public final class CountryLanguageEditPanel extends EntityEditPanel {

  public CountryLanguageEditPanel(SwingEntityEditModel editModel) {
    super(editModel);
  }

  @Override
  protected void initializeUI() {
    setInitialFocusProperty(World.COUNTRYLANGUAGE_COUNTRY_FK);

    setPreferredWidth(createForeignKeyComboBox(World.COUNTRYLANGUAGE_COUNTRY_FK), 120);
    createTextField(World.COUNTRYLANGUAGE_LANGUAGE).setColumns(12);
    createCheckBox(World.COUNTRYLANGUAGE_ISOFFICIAL, null, false);
    createTextField(World.COUNTRYLANGUAGE_PERCENTAGE);

    setLayout(gridLayout(2, 4));

    addPropertyPanel(World.COUNTRYLANGUAGE_COUNTRY_FK);
    addPropertyPanel(World.COUNTRYLANGUAGE_LANGUAGE);
    addPropertyPanel(World.COUNTRYLANGUAGE_ISOFFICIAL);
    addPropertyPanel(World.COUNTRYLANGUAGE_PERCENTAGE);
  }
}
package org.jminor.framework.demos.world.ui;

import org.jminor.common.model.table.ColumnSummary;
import org.jminor.framework.demos.world.domain.World;
import org.jminor.framework.demos.world.model.CountryCustomModel;
import org.jminor.framework.demos.world.model.CountryEditModel;
import org.jminor.framework.demos.world.model.CountryTableModel;
import org.jminor.swing.common.ui.control.Controls;
import org.jminor.swing.common.ui.dialog.Modal;
import org.jminor.swing.framework.model.SwingEntityModel;
import org.jminor.swing.framework.ui.EntityPanel;

import org.jfree.chart.ChartPanel;

import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import java.awt.BorderLayout;
import java.awt.Dimension;

import static org.jminor.swing.common.ui.dialog.Dialogs.displayInDialog;
import static org.jminor.swing.common.ui.layout.Layouts.borderLayout;
import static org.jminor.swing.common.ui.layout.Layouts.gridLayout;

public final class CountryCustomPanel extends EntityPanel {

  public CountryCustomPanel(final CountryCustomModel entityModel) {
    super(entityModel,
            new CountryEditPanel((CountryEditModel) entityModel.getEditModel()),
            new CountryTablePanel((CountryTableModel) entityModel.getTableModel()));
  }

  @Override
  protected void initializeUI() {
    SwingEntityModel countryModel = getModel();
    countryModel.getTableModel().getColumnSummaryModel(World.COUNTRY_POPULATION).setSummary(ColumnSummary.SUM);
    SwingEntityModel cityModel = countryModel.getDetailModel(World.T_CITY);
    cityModel.getTableModel().getColumnSummaryModel(World.CITY_POPULATION).setSummary(ColumnSummary.SUM);
    SwingEntityModel countryLanguageModel = countryModel.getDetailModel(World.T_COUNTRYLANGUAGE);

    countryModel.addLinkedDetailModel(cityModel);
    countryModel.addLinkedDetailModel(countryLanguageModel);

    CountryEditPanel countryEditPanel = (CountryEditPanel) getEditPanel();
    CountryTablePanel countryTablePanel = (CountryTablePanel) getTablePanel();
    countryTablePanel.getTable().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
    countryTablePanel.getTable().setDoubleClickAction(Controls.control(this::displayEditPanel));
    countryTablePanel.setSummaryPanelVisible(true);

    EntityPanel cityPanel = new EntityPanel(cityModel);
    cityPanel.getTablePanel().getTable().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
    cityPanel.getTablePanel().setSummaryPanelVisible(true);
    cityPanel.getTablePanel().setIncludeSouthPanel(false);

    EntityPanel languagePanel = new EntityPanel(countryLanguageModel);
    languagePanel.getTablePanel().getTable().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
    languagePanel.getTablePanel().setIncludeSouthPanel(false);

    addDetailPanels(cityPanel, languagePanel);

    countryEditPanel.initializePanel();
    countryTablePanel.initializePanel();
    cityPanel.initializePanel();
    languagePanel.initializePanel();

    ChartPanel cityChartPanel = countryTablePanel.getCityChartPanel();
    cityChartPanel.setPreferredSize(new Dimension(300, 300));
    ChartPanel languageChartPanel = countryTablePanel.getLanguageChartPanel();
    languageChartPanel.setPreferredSize(new Dimension(300, 300));

    JPanel southTablePanel = new JPanel(gridLayout(1, 2));
    southTablePanel.add(cityPanel);
    southTablePanel.add(languagePanel);

    JPanel southChartPanel = new JPanel(gridLayout(1, 2));
    southChartPanel.add(cityChartPanel);
    southChartPanel.add(languageChartPanel);

    JTabbedPane southTabbedPane = new JTabbedPane(SwingConstants.BOTTOM);
    southTabbedPane.addTab("Tables", southTablePanel);
    southTabbedPane.addTab("Charts", southChartPanel);
    southTabbedPane.setMnemonicAt(0, 'T');
    southTabbedPane.setMnemonicAt(1, 'C');

    setLayout(borderLayout());

    add(countryTablePanel, BorderLayout.CENTER);
    add(southTabbedPane, BorderLayout.SOUTH);

    initializeEditControlPanel();
    initializeKeyboardActions();
    initializeNavigation();
  }

  private void displayEditPanel() {
    final JPanel editPanel = getEditControlPanel();
    if (!editPanel.isShowing()) {
      displayInDialog(this, editPanel, Modal.NO);
    }
    getEditPanel().requestInitialFocus();
  }
}
package org.jminor.framework.demos.world.ui;

import org.jminor.framework.demos.world.model.ContinentModel;
import org.jminor.swing.framework.ui.EntityPanel;
import org.jminor.swing.framework.ui.EntityTablePanel;

import org.jfree.chart.ChartPanel;

import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.Dimension;

import static org.jfree.chart.ChartFactory.createBarChart;
import static org.jfree.chart.ChartFactory.createPieChart;
import static org.jminor.swing.common.ui.Components.setPreferredHeight;
import static org.jminor.swing.common.ui.layout.Layouts.borderLayout;
import static org.jminor.swing.common.ui.layout.Layouts.gridLayout;

public final class ContinentPanel extends EntityPanel {

  public ContinentPanel(ContinentModel entityModel) {
    super(entityModel, new ContinentTablePanel(entityModel.getTableModel()));
  }

  @Override
  protected void initializeUI() {
    ContinentModel model = (ContinentModel) getModel();

    EntityTablePanel tablePanel = getTablePanel();
    tablePanel.initializePanel();
    setPreferredHeight(tablePanel, 200);

    ChartPanel populationChartPanel = new ChartPanel(createPieChart("Population",
            model.getPopulationDataset()));
    populationChartPanel.setPreferredSize(new Dimension(300, 300));
    ChartPanel surfaceAreaChartPanel = new ChartPanel(createPieChart("Surface area",
            model.getSurfaceAreaDataset()));
    surfaceAreaChartPanel.setPreferredSize(new Dimension(300, 300));
    ChartPanel gnpChartPanel = new ChartPanel(createPieChart("GNP",
            model.getGnpDataset()));
    gnpChartPanel.setPreferredSize(new Dimension(300, 300));
    ChartPanel lifeExpectancyChartPanel = new ChartPanel(createBarChart("Life expectancy",
            "Continent", "Years",
            model.getLifeExpectancyDataset()));

    JPanel centerPanel = new JPanel(borderLayout());
    centerPanel.add(tablePanel, BorderLayout.NORTH);
    centerPanel.add(lifeExpectancyChartPanel, BorderLayout.CENTER);

    JPanel southChartPanel = new JPanel(gridLayout(1, 3));
    southChartPanel.add(populationChartPanel);
    southChartPanel.add(surfaceAreaChartPanel);
    southChartPanel.add(gnpChartPanel);

    setLayout(borderLayout());

    add(centerPanel, BorderLayout.CENTER);
    add(southChartPanel, BorderLayout.SOUTH);

    initializeKeyboardActions();
    initializeNavigation();
  }
}
package org.jminor.framework.demos.world.ui;

import org.jminor.swing.common.ui.control.ControlSet;
import org.jminor.swing.framework.model.SwingEntityTableModel;
import org.jminor.swing.framework.ui.EntityTablePanel;

import javax.swing.JTable;
import java.util.List;

public final class ContinentTablePanel extends EntityTablePanel {

  public ContinentTablePanel(SwingEntityTableModel tableModel) {
    super(tableModel);
    setIncludeSouthPanel(false);
    getTable().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
  }

  @Override
  protected ControlSet getPopupControls(List<ControlSet> additionalPopupControlSets) {
    return new ControlSet(getRefreshControl());
  }
}
package org.jminor.framework.demos.world.ui;

import org.jminor.framework.demos.world.model.LookupTableModel;
import org.jminor.swing.common.ui.control.ControlSet;
import org.jminor.swing.common.ui.control.Controls;
import org.jminor.swing.framework.ui.EntityTablePanel;

import java.io.File;
import java.io.IOException;
import java.util.List;

import static org.jminor.swing.common.ui.dialog.Dialogs.selectFileToSave;
import static org.jminor.swing.common.ui.worker.ProgressWorker.runWithProgressBar;

public final class LookupTablePanel extends EntityTablePanel {

  public LookupTablePanel(LookupTableModel tableModel) {
    super(tableModel);
    setConditionPanelVisible(true);
  }

  @Override
  protected ControlSet getPopupControls(List<ControlSet> additionalPopupControlSets) {
    ControlSet controls = super.getPopupControls(additionalPopupControlSets);
    controls.addSeparatorAt(2);
    controls.addAt(3, Controls.control(this::exportCSV, "Export CSV..."));

    return controls;
  }

  private void exportCSV() throws IOException {
    File fileToSave = selectFileToSave(this, null, "export.csv");
    runWithProgressBar(this, "Exporting data",
            "Export successful", "Export failed",
            () -> ((LookupTableModel) getTableModel()).exportCSV(fileToSave));
  }
}

3.1. Main application panel

package org.jminor.framework.demos.world.ui;

import org.jminor.common.model.CancelException;
import org.jminor.common.user.Users;
import org.jminor.framework.db.EntityConnectionProvider;
import org.jminor.framework.demos.world.domain.World;
import org.jminor.framework.demos.world.model.CountryCustomModel;
import org.jminor.framework.demos.world.model.CountryModel;
import org.jminor.framework.demos.world.model.WorldAppModel;
import org.jminor.swing.common.ui.Windows;
import org.jminor.swing.framework.model.SwingEntityModelBuilder;
import org.jminor.swing.framework.ui.EntityApplicationPanel;
import org.jminor.swing.framework.ui.EntityPanel;
import org.jminor.swing.framework.ui.EntityPanelBuilder;

import java.util.Locale;

public final class WorldAppPanel extends EntityApplicationPanel<WorldAppModel> {

  @Override
  protected void setupEntityPanelBuilders() {
    final SwingEntityModelBuilder countryModelBuilder = new SwingEntityModelBuilder(World.T_COUNTRY);
    countryModelBuilder.setModelClass(CountryModel.class);
    EntityPanelBuilder countryPanelBuilder = new EntityPanelBuilder(countryModelBuilder);
    countryPanelBuilder.setEditPanelClass(CountryEditPanel.class);
    countryPanelBuilder.setTablePanelClass(CountryTablePanel.class);

    final SwingEntityModelBuilder countryCustomModelBuilder = new SwingEntityModelBuilder(World.T_COUNTRY);
    countryCustomModelBuilder.setModelClass(CountryCustomModel.class);
    EntityPanelBuilder countryCustomPanelBuilder = new EntityPanelBuilder(countryCustomModelBuilder)
            .setPanelClass(CountryCustomPanel.class)
            .setCaption("Custom Country");

    EntityPanelBuilder cityPanelBuilder = new EntityPanelBuilder(World.T_CITY);
    cityPanelBuilder.setEditPanelClass(CityEditPanel.class);

    EntityPanelBuilder countryLanguagePanelBuilder = new EntityPanelBuilder(World.T_COUNTRYLANGUAGE);
    countryLanguagePanelBuilder.setEditPanelClass(CountryLanguageEditPanel.class);

    countryPanelBuilder.addDetailPanelBuilder(cityPanelBuilder);
    countryPanelBuilder.addDetailPanelBuilder(countryLanguagePanelBuilder);

    EntityPanelBuilder continentPanelBuilder = new EntityPanelBuilder(World.T_CONTINENT)
            .setPanelClass(ContinentPanel.class);
    EntityPanelBuilder lookupPanelBuilder = new EntityPanelBuilder(World.T_LOOKUP)
            .setTablePanelClass(LookupTablePanel.class)
            .setRefreshOnInit(false);

    addEntityPanelBuilders(countryPanelBuilder, countryCustomPanelBuilder, continentPanelBuilder, lookupPanelBuilder);
  }

  @Override
  protected WorldAppModel initializeApplicationModel(EntityConnectionProvider connectionProvider) {
    return new WorldAppModel(connectionProvider);
  }

  public static void main(final String[] args) throws CancelException {
    Locale.setDefault(new Locale("en", "EN"));
    EntityPanel.TOOLBAR_BUTTONS.set(true);
    EntityConnectionProvider.CLIENT_DOMAIN_CLASS.set("org.jminor.framework.demos.world.domain.World");
    new WorldAppPanel().startApplication("World", null, false,
            Windows.getScreenSizeRatio(0.8), Users.parseUser("scott:tiger"));
  }
}