1. Screenshots

Departments
departments

2. Database

Create Schema SQL
create user if not exists scott password 'tiger';
alter user scott admin true;

create schema employees;

CREATE TABLE employees.department (
  department_no INT NOT NULL,
  name VARCHAR(14) NOT NULL,
  location VARCHAR(13),
  constraint department_pk primary key (department_no)
);

CREATE TABLE employees.employee(
  id INT NOT NULL,
  name VARCHAR(10) NOT NULL,
  job VARCHAR(9),
  manager_id INT,
  hiredate DATE,
  salary DECIMAL(7, 2) NOT NULL,
  commission DECIMAL(7, 2),
  department_no INT NOT NULL,
  constraint employee_pk primary key (id),
  constraint employee_department_fk foreign key (department_no) references employees.department(department_no),
  constraint employee_manager_fk foreign key (manager_id) references employees.employee(id)
);

CREATE SEQUENCE employees.employee_seq START WITH 17;

INSERT INTO employees.department(department_no, name, location)
VALUES (10, 'Accounting', 'New York'),
  (20, 'Research', 'Dallas'),
  (30, 'Sales', 'Chicaco'),
  (40, 'Operations', 'Boston');

INSERT INTO employees.employee(id, name, job, manager_id, hiredate, salary, commission, department_no)
VALUES (8, 'King', 'President', NULL, '1981-11-17', 5000, NULL, 10),
  (3, 'Jones', 'Manager', 8, '1981-04-02', 2975, NULL, 20),
  (5, 'Blake', 'Manager', 3, '1981-05-01', 2850, NULL, 10),
  (1, 'Allen', 'Salesman', 5, '1981-02-20', 1600, 300, 30),
  (2, 'Ward', 'Salesman', 8, '1981-02-22', 1250, 500, 30),
  (4, 'Martin', 'Salesman', 3, '1981-09-28', 1250, 1400, 30),
  (6, 'Clark', 'Manager', 8, '1981-06-09', 2450, 1500, 10),
  (7, 'Scott', 'Analyst', 3, '1987-04-19', 3000, 1500, 20),
  (9, 'Turner', 'Salesman', 5, '1981-09-08', 1500, 0, 30),
  (10, 'Adams', 'Clerk', 3, '1988-05-15', 1100, NULL, 20),
  (11, 'James', 'Clerk', 5, '1996-10-03', 950, 1500, 10),
  (12, 'Ford', 'Clerk', 3, '1988-12-12', 3200, 1200, 20),
  (13, 'Miller', 'Clerk', 6, '1983-01-23', 1300, 1200, 10),
  (14, 'Ben', 'Clerk', 3, '1989-12-12', 1600, 1200, 20),
  (15, 'Baker', 'Clerk', 5, '2007-01-01', 1000, NULL, 10),
  (16, 'Trevor', 'Clerk', 5, '2007-01-01', 1000, NULL, 10);

commit;

3. Domain model

We start by creating a class named Employees in a package of our choosing, extending DefaultDomain and define the identity and column constants required for the department Entity, which is based on the EMPLOYEES.DEPARTMENT table. The EntityType constant represents the entity type, the columns represent the columns in the table. The entityType in this example contains the actual table name (employees.department), but the table name can be specified via the tableName() builder method when the entity is defined. The column names are the actual column names from the table, but these can also be specified via columnName() builder method.

/**
 * This class contains the specification for the Employees application domain model
 */
public final class Employees extends DefaultDomain {

  /** The domain type identifying this domain model */
  public static final DomainType DOMAIN = domainType(Employees.class);

  /** Entity type for the table employees.department */
  public interface Department extends Entity {
    EntityType TYPE = DOMAIN.entityType("employees.department", Department.class);

    /** Columns for the columns in the employees.department table */
    Column<Integer> DEPARTMENT_NO = TYPE.integerColumn("department_no");
    Column<String> NAME = TYPE.stringColumn("name");
    Column<String> LOCATION = TYPE.stringColumn("location");

    /** Bean getters and setters */
    Integer getDepartmentNo();

    void setDepartmentNo(Integer departmentNo);

    String getName();

    void setName(String name);

    String getLocation();

    void setLocation(String location);
  }

Next we define the constants required for the employee entity, which is based on the EMPLOYEES.EMPLOYEE table. Here there are two additional attributes with _FK suffixes, we’ll use these later when we specify the foreign key properties for the employee entity.

  /** Entity type for the table employees.employee */
  public interface Employee extends Entity {
    EntityType TYPE = DOMAIN.entityType("employees.employee", Employee.class);

    /** Columns for the columns in the employees.employee table */
    Column<Integer> ID = TYPE.integerColumn("id");
    Column<String> NAME = TYPE.stringColumn("name");
    Column<String> JOB = TYPE.stringColumn("job");
    Column<Integer> MANAGER_ID = TYPE.integerColumn("manager_id");
    Column<LocalDate> HIREDATE = TYPE.localDateColumn("hiredate");
    Column<BigDecimal> SALARY = TYPE.bigDecimalColumn("salary");
    Column<Double> COMMISSION = TYPE.doubleColumn("commission");
    Column<Integer> DEPARTMENT = TYPE.integerColumn("department_no");

    /** Foreign key attribute for the DEPTNO column in the table employees.employee */
    ForeignKey DEPARTMENT_FK = TYPE.foreignKey("department_no_fk", DEPARTMENT, Department.DEPARTMENT_NO);
    /** Foreign key attribute for the MGR column in the table employees.employee */
    ForeignKey MANAGER_FK = TYPE.foreignKey("manager_fk", MANAGER_ID, Employee.ID);
    /** Attribute for the denormalized department location property */
    Attribute<String> DEPARTMENT_LOCATION = TYPE.stringAttribute("location");

    JRReportType EMPLOYEE_REPORT = JasperReports.reportType("employee_report");

    List<Item<String>> JOB_VALUES = asList(
            item("Analyst"), item("Clerk"),
            item("Manager"), item("President"),
            item("Salesman"));

    /** Bean getters and setters */
    Integer getId();

    void setId(Integer id);

    String getName();

    void setName(String name);

    String getJob();

    void setJob(String job);

    Employee getManager();

    void setManager(Employee manager);

    LocalDate getHiredate();

    void setHiredate(LocalDate hiredate);

    BigDecimal getSalary();

    void setSalary(BigDecimal salary);

    Double getCommission();

    void setCommission(Double commission);

    Department getDepartment();

    void setDepartment(Department department);
  }

In this section we add a constructor, calling the two methods adding the entity definitions to the domain model.

  /** Initializes this domain model */
  public Employees() {
    super(DOMAIN);
    department();
    employee();
  }

Next we add the method defining the department entity. EntityDefinition.Builder instances are provided by the EntityDefinition class, via the definition method, which takes an array of AttributeDefinition.Builder instances as parameter. For an overview of the AttributeDefinition class see Manual#Attributes. The EntityDefinition.Builder class provides chained setters for configuring the entity, such as the primary key value source. Here we set the default order by, the so-called stringFactory which is responsible for providing toString() implementations for entities, the smallDataset attribute which hints that it is OK to automatically base ComboBoxes on the entity, and we also set the caption. In this case we simply have the department string provider return the department name.

  void department() {
    /*Defining the entity Department.TYPE*/
    add(Department.TYPE.define(
            Department.DEPARTMENT_NO.define()
                    .primaryKey()
                    .caption("No.")
                    .nullable(false)
                    .beanProperty("departmentNo"),
            Department.NAME.define()
                    .column()
                    .caption("Name")
                    .maximumLength(14)
                    .nullable(false)
                    .beanProperty("name"),
            Department.LOCATION.define()
                    .column()
                    .caption("Location")
                    .maximumLength(13)
                    .beanProperty("location"))
            .smallDataset(true)
            .orderBy(ascending(Department.NAME))
            .stringFactory(Department.NAME)
            .caption("Department"));
  }

Next we define the employee entity. Here we set the keyGenerator to KeyGenerator.sequence("employees.employee_seq") which, as the name suggests, fetches a value from a sequence. Here we also introduce the ForeignKey, the orderBy, as well as a ColorProvider which is responsible for providing a custom color for an entity instance.

  void employee() {
    /*Defining the entity Employee.TYPE*/
    add(Employee.TYPE.define(
            Employee.ID.define()
                    .primaryKey()
                    .beanProperty("id"),
            Employee.NAME.define()
                    .column()
                    .caption("Name")
                    .searchable(true)
                    .maximumLength(10)
                    .nullable(false)
                    .beanProperty("name"),
            Employee.DEPARTMENT.define()
                    .column()
                    .nullable(false),
            Employee.DEPARTMENT_FK.define()
                    .foreignKey()
                    .caption("Department")
                    .beanProperty("department"),
            Employee.JOB.define()
                    .column()
                    .caption("Job")
                    .items(Employee.JOB_VALUES)
                    .beanProperty("job"),
            Employee.SALARY.define()
                    .column()
                    .caption("Salary")
                    .nullable(false)
                    .valueRange(900, 10000)
                    .maximumFractionDigits(2)
                    .beanProperty("salary"),
            Employee.COMMISSION.define()
                    .column()
                    .caption("Commission")
                    .valueRange(100, 2000)
                    .maximumFractionDigits(2)
                    .beanProperty("commission"),
            Employee.MANAGER_ID.define()
                    .column(),
            Employee.MANAGER_FK.define()
                    .foreignKey()
                    .caption("Manager")
                    .beanProperty("manager"),
            Employee.HIREDATE.define()
                    .column()
                    .caption("Hiredate")
                    .nullable(false)
                    .beanProperty("hiredate")
                    .localeDateTimePattern(LocaleDateTimePattern.builder()
                            .delimiterDash()
                            .yearFourDigits()
                            .build()),
            Employee.DEPARTMENT_LOCATION.define()
                    .denormalized(Employee.DEPARTMENT_FK, Department.LOCATION)
                    .caption("Location"))
            .keyGenerator(sequence("employees.employee_seq"))
            .orderBy(ascending(Employee.DEPARTMENT, Employee.NAME))
            .stringFactory(Employee.NAME)
            .caption("Employee")
            .backgroundColorProvider((entity, attribute) -> {
              if (attribute.equals(Employee.JOB) && "Manager".equals(entity.get(Employee.JOB))) {
                return Color.CYAN;
              }

              return null;
            })
            .foregroundColorProvider((entity, attribute) -> {
              if (attribute.equals(Employee.SALARY) && entity.get(Employee.SALARY).doubleValue() < 1300) {
                return Color.RED;
              }

              return null;
            }));

    add(Employee.EMPLOYEE_REPORT, classPathReport(Employees.class, "employees.jasper"));
  }
}

3.1. Domain unit test

To unit test the domain model we extend EntityTestUnit and create a public test method for each entity we want tested and within that method call test(entityType). The EntityTestUnit relies on information from the domain model to construct random entity instances and run insert, update, select and delete tests. The tests are run within their own transactions which are then rolled back. We can provide our own entity instances if we’d like by overriding initializeTestEntity(entityType, foreignKeyEntities) handling the cases we’d like and delegating the rest to the superclass implementation. The same can be done for entities that are referenced via foreign keys and are required in order to be able to insert records.

Note
The EntityTestUnit.TEST_USER configuration value specifies the user credentials to use when running the tests, you can set it directly or via the codion.test.user system property.
public class EmployeesTest extends EntityTestUnit {

  public EmployeesTest() {
    super(new Employees());
  }

  @Test
  void department() throws Exception {
    test(Department.TYPE);
  }

  @Test
  void employee() throws Exception {
    test(Employee.TYPE);
  }
}

Here is the parameter required for running the Employees test on the default H2 embedded database, assuming the database resides in the working directory.

-Dcodion.db.url=jdbc:h2:mem:h2db -Dcodion.test.user=scott:tiger

For further information on parameters required for running Codion applications see client configuration and server configuration.

4. Model

In many cases we can use the default model implementations, but here we will customize the employee edit model by extending SwingEntityEditModel.

We must provide a constructor with a single EntityConnectionProvider parameter. We also call the bindEvents method we define later on.

public final class EmployeeEditModel extends SwingEntityEditModel {

  public EmployeeEditModel(EntityConnectionProvider connectionProvider) {
    super(Employee.TYPE, connectionProvider);
    initializeComboBoxModels(Employee.MANAGER_FK, Employee.DEPARTMENT_FK);
    bindEvents();
  }

At some point we are going to be editing the manager attribute of an employee which means we’ll need a list of managers. We override createForeignKeyComboBoxModel in order to provide a specialized combo box model for the manager attribute.

Note
The foreign key (Employee.MGR_FK) is used when referring to the manager attribute, very rarely do we have to worry about the underlying reference attribute (Employee.MGR).

We start by calling the super class method which creates an unfiltered EntityComboBoxModel, then we configure the one for the manager foreign key so that it only displays employees with the job PRESIDENT or MANAGER.

  /** Providing a custom ComboBoxModel for the manager attribute, which only shows managers and the president */
  @Override
  public EntityComboBoxModel createForeignKeyComboBoxModel(ForeignKey foreignKey) {
    EntityComboBoxModel comboBoxModel = super.createForeignKeyComboBoxModel(foreignKey);
    if (foreignKey.equals(Employee.MANAGER_FK)) {
      //Customize the null value caption so that it displays 'None'
      //instead of the default '-' character
      comboBoxModel.setNullCaption("None");
      //we do not want filtering to remove a value that is selected
      //and thereby change the selection, see bindEvents() below
      comboBoxModel.filterSelectedItem().set(false);
      //Only select the president and managers from the database
      comboBoxModel.condition().set(() ->
              Employee.JOB.in("Manager", "President"));
    }

    return comboBoxModel;
  }

Finally, we bind a couple of events, for further information see Manual#Event binding.

  private void bindEvents() {
    //Refresh the manager ComboBoxModel when an employee is added, deleted or updated,
    //in case a new manager got hired, fired or promoted
    addInsertUpdateOrDeleteListener(() -> foreignKeyComboBoxModel(Employee.MANAGER_FK).refresh());
    //Filter the manager ComboBoxModel so that only managers from the selected department are shown,
    //this filtering happens each time the department value is changed, either when an employee is
    //selected or the department combo box selection changes
    addValueListener(Employee.DEPARTMENT_FK, department -> {
      //only show managers from the same department as the selected employee and hide the currently
      //selected employee to prevent an employee from being made her own manager
      foreignKeyComboBoxModel(Employee.MANAGER_FK).includeCondition().set(manager ->
              Objects.equals(manager.referencedEntity(Employee.DEPARTMENT_FK), department)
                      && !Objects.equals(manager, entity()));
    });
  }
}

5. UI

If we want to do any editing we must provide a EntityEditPanel implementation for the entity, we start by creating a DepartmentEditPanel.

A class extending EntityEditPanel must provide a constructor taking a single SwingEntityEditModel parameter.

public class DepartmentEditPanel extends EntityEditPanel {

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

We override the intializeUI method to construct the actual UI, used for editing the department entity. The EntityEditPanel class provides methods for creating all the basic controls required, named create…​, such as createTextField(attribute) or createForeignKeyComboBox(foreignKey).

  @Override
  protected void initializeUI() {
    initialFocusAttribute().set(Department.DEPARTMENT_NO);

    createTextField(Department.DEPARTMENT_NO)
            .columns(3)
            //don't allow editing of existing department numbers
            .enabled(editModel().exists().not());
    createTextField(Department.NAME)
            .columns(8);
    createTextField(Department.LOCATION)
            .columns(12);

    editModel().exists().addDataListener(exists ->
            initialFocusAttribute().set(exists ? Department.NAME: Department.DEPARTMENT_NO));

    setLayout(borderLayout());
    add(borderLayoutPanel()
            .northComponent(borderLayoutPanel()
                    .westComponent(createInputPanel(Department.DEPARTMENT_NO))
                    .centerComponent(createInputPanel(Department.NAME))
                    .build())
            .centerComponent(createInputPanel(Department.LOCATION))
            .build(), borderLayout().CENTER);
  }
}

We extend EntityTablePanel for the department entity in order to provide a report print action.

A class extending EntityTablePanel must provide a constructor taking a single SwingEntityTableModel parameter.

public class DepartmentTablePanel extends EntityTablePanel {

  public DepartmentTablePanel(SwingEntityTableModel tableModel) {
    super(tableModel);
  }

We create a method for viewing a report, which is called via an action we’ll initialize in the next step. For further information about report viewing and printing see Manual#Reporting with JasperReports.

  public void viewEmployeeReport() throws Exception {
    Collection<Integer> departmentNumbers =
            Entity.distinct(Department.DEPARTMENT_NO,
                    tableModel().selectionModel().getSelectedItems());
    Map<String, Object> reportParameters = new HashMap<>();
    reportParameters.put("DEPTNO", departmentNumbers);

    JasperPrint employeeReport = tableModel().connectionProvider().connection()
            .report(Employee.EMPLOYEE_REPORT, reportParameters);

    Dialogs.componentDialog(new JRViewer(employeeReport))
            .owner(this)
            .modal(false)
            .size(new Dimension(800, 600))
            .show();
  }

Next we override createPrintMenuControls() to add our report action to the popup print controls.

  @Override
  protected Controls createPrintMenuControls() {
    StateObserver selectionNotEmpty =
            tableModel().selectionModel().selectionNotEmpty();

    return super.createPrintMenuControls()
            .add(Control.builder(this::viewEmployeeReport)
                    .name("Employee Report")
                    .enabled(selectionNotEmpty)
                    .build());
  }
}

For editing employee entities we create the EmployeeEditPanel class.

public class EmployeeEditPanel extends EntityEditPanel {

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

All we have to do is override initializeUI.

  @Override
  protected void initializeUI() {
    initialFocusAttribute().set(Employee.NAME);

    createTextField(Employee.NAME)
            .columns(8);
    createItemComboBox(Employee.JOB);
    createForeignKeyComboBox(Employee.MANAGER_FK);
    createTextField(Employee.SALARY)
            .columns(5);
    createTextField(Employee.COMMISSION)
            .columns(5);
    createTemporalFieldPanel(Employee.HIREDATE)
            .columns(6);

    setLayout(flexibleGridLayout(0, 3));

    addInputPanel(Employee.NAME);
    addInputPanel(Employee.DEPARTMENT_FK, createDepartmentPanel());
    addInputPanel(Employee.JOB);

    addInputPanel(Employee.MANAGER_FK);
    add(gridLayoutPanel(1, 2)
            .add(createInputPanel(Employee.SALARY))
            .add(createInputPanel(Employee.COMMISSION))
            .build());
    addInputPanel(Employee.HIREDATE);
  }

  private JPanel createDepartmentPanel() {
    EntityComboBox departmentBox = createForeignKeyComboBox(Employee.DEPARTMENT_FK).build();
    NumberField<Integer> departmentNumberField = departmentBox.integerSelectorField(Department.DEPARTMENT_NO)
            .transferFocusOnEnter(true)
            .build();

    component(Employee.DEPARTMENT_FK).set(departmentNumberField);

    return Components.borderLayoutPanel()
            .westComponent(departmentNumberField)
            .centerComponent(departmentBox)
            .build();
  }
}

5.1. Main application model

We create a main application model by extending SwingEntityApplicationModel and add an entity model for the department entity.

public final class EmployeesAppModel extends SwingEntityApplicationModel {

  public EmployeesAppModel(EntityConnectionProvider connectionProvider) {
    super(connectionProvider);
    SwingEntityModel departmentModel = new SwingEntityModel(Department.TYPE, connectionProvider);
    departmentModel.addDetailModel(new SwingEntityModel(new EmployeeEditModel(connectionProvider)));
    departmentModel.tableModel().refresh();
    addEntityModel(departmentModel);
  }
}

5.2. Main application panel

We create a main application panel by extending EntityApplicationPanel. A constructor with a single EmployeesAppModel argument is required. Overriding createEntityPanels() we create two EntityPanels using the SwingEntityModels from the application model and return the department panel, which will act as the root panel.

public class EmployeesAppPanel extends EntityApplicationPanel<EmployeesAppModel> {

  private static final String DEFAULT_FLAT_LOOK_AND_FEEL = "com.formdev.flatlaf.intellijthemes.FlatArcIJTheme";

  public EmployeesAppPanel(EmployeesAppModel applicationModel) {
    super(applicationModel);
  }

  @Override
  protected List<EntityPanel> createEntityPanels() {
    SwingEntityModel departmentModel = applicationModel().entityModel(Department.TYPE);
    SwingEntityModel employeeModel = departmentModel.detailModel(Employee.TYPE);

    EntityPanel employeePanel = new EntityPanel(employeeModel,
            new EmployeeEditPanel(employeeModel.editModel()));

    EntityPanel departmentPanel = new EntityPanel(departmentModel,
            new DepartmentEditPanel(departmentModel.editModel()),
            new DepartmentTablePanel(departmentModel.tableModel()),
            splitPaneResizeWeight(0.4));
    departmentPanel.addDetailPanel(employeePanel);

    return singletonList(departmentPanel);
  }

Next we add a method for importing a JSON text file, which we’ll call via an action initialized in the next section.

  public void importJSON() throws Exception {
    File file = Dialogs.fileSelectionDialog()
            .owner(this)
            .fileFilter(new FileNameExtensionFilter("JSON files", "json"))
            .fileFilter(new FileNameExtensionFilter("Text files", "txt"))
            .selectFile();

    List<Entity> entities = entityObjectMapper(applicationModel().entities())
            .deserializeEntities(textFileContents(file.getAbsolutePath(), defaultCharset()));

    SwingEntityTableModel tableModel = SwingEntityTableModel.tableModel(entities, applicationModel().connectionProvider());
    tableModel.editModel().readOnly().set(true);
    EntityTablePanel tablePanel = new EntityTablePanel(tableModel);
    tablePanel.configure().includePopupMenu(false);

    Dialogs.componentDialog(tablePanel.initialize())
            .owner(this)
            .title("Import")
            .show();
  }

We override createToolsMenuControls() to add our import action to the Tools menu.

  @Override
  protected Controls createToolsMenuControls() {
    return super.createToolsMenuControls()
            .add(Control.builder(this::importJSON)
                    .name("Import JSON")
                    .build());
  }

We create a main() method for configuring and running the application. See client configuration for what configuration is required for running the client.

  public static void main(String[] args) {
    EntityPanel.TOOLBAR_CONTROLS.set(true);
    Arrays.stream(FlatAllIJThemes.INFOS)
            .forEach(LookAndFeelProvider::addLookAndFeelProvider);
    EntityApplicationPanel.builder(EmployeesAppModel.class, EmployeesAppPanel.class)
            .applicationName("Employees")
            .domainType(Employees.DOMAIN)
            .defaultLookAndFeelClassName(DEFAULT_FLAT_LOOK_AND_FEEL)
            .frameSize(new Dimension(1000, 600))
            .defaultLoginUser(User.parse("scott:tiger"))
            .start();
  }

6. Load test

public final class EmployeesLoadTest extends EntityLoadTestModel<EmployeesAppModel> {

  private static final User UNIT_TEST_USER =
          User.parse(System.getProperty("codion.test.user", "scott:tiger"));

  public EmployeesLoadTest() {
    super(UNIT_TEST_USER, asList(new InsertDepartment(), new InsertEmployee(), new LoginLogout(),
            new SelectDepartment(), new UpdateEmployee()));
  }

  @Override
  protected EmployeesAppModel createApplication(User user) throws CancelException {
    EmployeesAppModel applicationModel =
            new EmployeesAppModel(EntityConnectionProvider.builder()
                    .domainType(Employees.DOMAIN)
                    .clientTypeId(EmployeesLoadTest.class.getSimpleName())
                    .user(user)
                    .build());

    SwingEntityModel model = applicationModel.entityModel(Department.TYPE);
    model.detailModelLink(model.detailModel(Employee.TYPE)).active().set(true);
    try {
      model.tableModel().refresh();
    }
    catch (Exception ignored) {/*ignored*/}

    return applicationModel;
  }

  public static void main(String[] args) {
    new LoadTestPanel<>(new EmployeesLoadTest().loadTestModel()).run();
  }
}
public final class InsertDepartment extends AbstractEntityUsageScenario<EmployeesAppModel> {

  @Override
  protected void perform(EmployeesAppModel application) throws Exception {
    SwingEntityModel departmentModel = application.entityModel(Department.TYPE);
    departmentModel.editModel().set(createRandomEntity(application.entities(), Department.TYPE, null));
    departmentModel.editModel().insert();
  }
}
public final class InsertEmployee extends AbstractEntityUsageScenario<EmployeesAppModel> {

  @Override
  protected void perform(EmployeesAppModel application) throws Exception {
    SwingEntityModel departmentModel = application.entityModel(Department.TYPE);
    selectRandomRow(departmentModel.tableModel());
    SwingEntityModel employeeModel = departmentModel.detailModel(Employee.TYPE);
    Map<ForeignKey, Entity> foreignKeyEntities = new HashMap<>();
    foreignKeyEntities.put(Employee.DEPARTMENT_FK, departmentModel.tableModel().selectionModel().getSelectedItem());
    employeeModel.editModel().set(createRandomEntity(application.entities(), Employee.TYPE, foreignKeyEntities));
    employeeModel.editModel().insert();
  }

  @Override
  public int defaultWeight() {
    return 3;
  }
}
public final class LoginLogout extends AbstractEntityUsageScenario<EmployeesAppModel> {

  final Random random = new Random();

  @Override
  protected void perform(EmployeesAppModel application) {
    try {
      application.connectionProvider().close();
      Thread.sleep(random.nextInt(1500));
      application.connectionProvider().connection();
    }
    catch (InterruptedException ignored) {/*ignored*/}
  }

  @Override
  public int defaultWeight() {
    return 4;
  }
}
public final class SelectDepartment extends AbstractEntityUsageScenario<EmployeesAppModel> {

  @Override
  protected void perform(EmployeesAppModel application) {
    selectRandomRow(application.entityModel(Department.TYPE).tableModel());
  }

  @Override
  public int defaultWeight() {
    return 10;
  }
}
public final class UpdateEmployee extends AbstractEntityUsageScenario<EmployeesAppModel> {

  private final Random random = new Random();

  @Override
  protected void perform(EmployeesAppModel application) throws Exception {
    SwingEntityModel departmentModel = application.entityModel(Department.TYPE);
    selectRandomRow(departmentModel.tableModel());
    SwingEntityModel employeeModel = departmentModel.detailModel(Employee.TYPE);
    if (employeeModel.tableModel().getRowCount() > 0) {
      EntityConnection connection = employeeModel.connectionProvider().connection();
      connection.beginTransaction();
      try {
        selectRandomRow(employeeModel.tableModel());
        Entity selected = employeeModel.tableModel().selectionModel().getSelectedItem();
        randomize(application.entities(), selected, null);
        employeeModel.editModel().set(selected);
        employeeModel.editModel().update();
        selectRandomRow(employeeModel.tableModel());
        selected = employeeModel.tableModel().selectionModel().getSelectedItem();
        randomize(application.entities(), selected, null);
        employeeModel.editModel().set(selected);
        employeeModel.editModel().update();
      }
      finally {
        if (random.nextBoolean()) {
          connection.rollbackTransaction();
        }
        else {
          connection.commitTransaction();
        }
      }
    }
  }

  @Override
  public int defaultWeight() {
    return 5;
  }
}

7. Full Demo Source

7.1. Domain

/*
 * This file is part of Codion.
 *
 * Codion is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Codion is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Codion.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright (c) 2004 - 2024, Björn Darri Sigurðsson.
 */
package is.codion.framework.demos.employees.domain;

import is.codion.common.format.LocaleDateTimePattern;
import is.codion.common.item.Item;
import is.codion.framework.domain.DefaultDomain;
import is.codion.framework.domain.DomainType;
import is.codion.framework.domain.entity.Entity;
import is.codion.framework.domain.entity.EntityType;
import is.codion.framework.domain.entity.attribute.Attribute;
import is.codion.framework.domain.entity.attribute.Column;
import is.codion.framework.domain.entity.attribute.ForeignKey;
import is.codion.plugin.jasperreports.JRReportType;
import is.codion.plugin.jasperreports.JasperReports;

import java.awt.Color;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;

import static is.codion.common.item.Item.item;
import static is.codion.framework.domain.DomainType.domainType;
import static is.codion.framework.domain.entity.KeyGenerator.sequence;
import static is.codion.framework.domain.entity.OrderBy.ascending;
import static is.codion.plugin.jasperreports.JasperReports.classPathReport;
import static java.util.Arrays.asList;

/**
 * This class contains the specification for the Employees application domain model
 */
public final class Employees extends DefaultDomain {

  /** The domain type identifying this domain model */
  public static final DomainType DOMAIN = domainType(Employees.class);

  /** Entity type for the table employees.department */
  public interface Department extends Entity {
    EntityType TYPE = DOMAIN.entityType("employees.department", Department.class);

    /** Columns for the columns in the employees.department table */
    Column<Integer> DEPARTMENT_NO = TYPE.integerColumn("department_no");
    Column<String> NAME = TYPE.stringColumn("name");
    Column<String> LOCATION = TYPE.stringColumn("location");

    /** Bean getters and setters */
    Integer getDepartmentNo();

    void setDepartmentNo(Integer departmentNo);

    String getName();

    void setName(String name);

    String getLocation();

    void setLocation(String location);
  }

  /** Entity type for the table employees.employee */
  public interface Employee extends Entity {
    EntityType TYPE = DOMAIN.entityType("employees.employee", Employee.class);

    /** Columns for the columns in the employees.employee table */
    Column<Integer> ID = TYPE.integerColumn("id");
    Column<String> NAME = TYPE.stringColumn("name");
    Column<String> JOB = TYPE.stringColumn("job");
    Column<Integer> MANAGER_ID = TYPE.integerColumn("manager_id");
    Column<LocalDate> HIREDATE = TYPE.localDateColumn("hiredate");
    Column<BigDecimal> SALARY = TYPE.bigDecimalColumn("salary");
    Column<Double> COMMISSION = TYPE.doubleColumn("commission");
    Column<Integer> DEPARTMENT = TYPE.integerColumn("department_no");

    /** Foreign key attribute for the DEPTNO column in the table employees.employee */
    ForeignKey DEPARTMENT_FK = TYPE.foreignKey("department_no_fk", DEPARTMENT, Department.DEPARTMENT_NO);
    /** Foreign key attribute for the MGR column in the table employees.employee */
    ForeignKey MANAGER_FK = TYPE.foreignKey("manager_fk", MANAGER_ID, Employee.ID);
    /** Attribute for the denormalized department location property */
    Attribute<String> DEPARTMENT_LOCATION = TYPE.stringAttribute("location");

    JRReportType EMPLOYEE_REPORT = JasperReports.reportType("employee_report");

    List<Item<String>> JOB_VALUES = asList(
            item("Analyst"), item("Clerk"),
            item("Manager"), item("President"),
            item("Salesman"));

    /** Bean getters and setters */
    Integer getId();

    void setId(Integer id);

    String getName();

    void setName(String name);

    String getJob();

    void setJob(String job);

    Employee getManager();

    void setManager(Employee manager);

    LocalDate getHiredate();

    void setHiredate(LocalDate hiredate);

    BigDecimal getSalary();

    void setSalary(BigDecimal salary);

    Double getCommission();

    void setCommission(Double commission);

    Department getDepartment();

    void setDepartment(Department department);
  }

  /** Initializes this domain model */
  public Employees() {
    super(DOMAIN);
    department();
    employee();
  }

  void department() {
    /*Defining the entity Department.TYPE*/
    add(Department.TYPE.define(
            Department.DEPARTMENT_NO.define()
                    .primaryKey()
                    .caption("No.")
                    .nullable(false)
                    .beanProperty("departmentNo"),
            Department.NAME.define()
                    .column()
                    .caption("Name")
                    .maximumLength(14)
                    .nullable(false)
                    .beanProperty("name"),
            Department.LOCATION.define()
                    .column()
                    .caption("Location")
                    .maximumLength(13)
                    .beanProperty("location"))
            .smallDataset(true)
            .orderBy(ascending(Department.NAME))
            .stringFactory(Department.NAME)
            .caption("Department"));
  }

  void employee() {
    /*Defining the entity Employee.TYPE*/
    add(Employee.TYPE.define(
            Employee.ID.define()
                    .primaryKey()
                    .beanProperty("id"),
            Employee.NAME.define()
                    .column()
                    .caption("Name")
                    .searchable(true)
                    .maximumLength(10)
                    .nullable(false)
                    .beanProperty("name"),
            Employee.DEPARTMENT.define()
                    .column()
                    .nullable(false),
            Employee.DEPARTMENT_FK.define()
                    .foreignKey()
                    .caption("Department")
                    .beanProperty("department"),
            Employee.JOB.define()
                    .column()
                    .caption("Job")
                    .items(Employee.JOB_VALUES)
                    .beanProperty("job"),
            Employee.SALARY.define()
                    .column()
                    .caption("Salary")
                    .nullable(false)
                    .valueRange(900, 10000)
                    .maximumFractionDigits(2)
                    .beanProperty("salary"),
            Employee.COMMISSION.define()
                    .column()
                    .caption("Commission")
                    .valueRange(100, 2000)
                    .maximumFractionDigits(2)
                    .beanProperty("commission"),
            Employee.MANAGER_ID.define()
                    .column(),
            Employee.MANAGER_FK.define()
                    .foreignKey()
                    .caption("Manager")
                    .beanProperty("manager"),
            Employee.HIREDATE.define()
                    .column()
                    .caption("Hiredate")
                    .nullable(false)
                    .beanProperty("hiredate")
                    .localeDateTimePattern(LocaleDateTimePattern.builder()
                            .delimiterDash()
                            .yearFourDigits()
                            .build()),
            Employee.DEPARTMENT_LOCATION.define()
                    .denormalized(Employee.DEPARTMENT_FK, Department.LOCATION)
                    .caption("Location"))
            .keyGenerator(sequence("employees.employee_seq"))
            .orderBy(ascending(Employee.DEPARTMENT, Employee.NAME))
            .stringFactory(Employee.NAME)
            .caption("Employee")
            .backgroundColorProvider((entity, attribute) -> {
              if (attribute.equals(Employee.JOB) && "Manager".equals(entity.get(Employee.JOB))) {
                return Color.CYAN;
              }

              return null;
            })
            .foregroundColorProvider((entity, attribute) -> {
              if (attribute.equals(Employee.SALARY) && entity.get(Employee.SALARY).doubleValue() < 1300) {
                return Color.RED;
              }

              return null;
            }));

    add(Employee.EMPLOYEE_REPORT, classPathReport(Employees.class, "employees.jasper"));
  }
}

7.2. Domain unit test

/*
 * This file is part of Codion.
 *
 * Codion is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Codion is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Codion.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright (c) 2004 - 2024, Björn Darri Sigurðsson.
 */
package is.codion.framework.demos.employees.domain;

import is.codion.framework.demos.employees.domain.Employees.Department;
import is.codion.framework.demos.employees.domain.Employees.Employee;
import is.codion.framework.domain.entity.test.EntityTestUnit;

import org.junit.jupiter.api.Test;

public class EmployeesTest extends EntityTestUnit {

  public EmployeesTest() {
    super(new Employees());
  }

  @Test
  void department() throws Exception {
    test(Department.TYPE);
  }

  @Test
  void employee() throws Exception {
    test(Employee.TYPE);
  }
}

7.3. Model

/*
 * This file is part of Codion.
 *
 * Codion is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Codion is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Codion.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright (c) 2004 - 2024, Björn Darri Sigurðsson.
 */
package is.codion.framework.demos.employees.model;

import is.codion.framework.db.EntityConnectionProvider;
import is.codion.framework.demos.employees.domain.Employees.Employee;
import is.codion.framework.domain.entity.attribute.ForeignKey;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.model.component.EntityComboBoxModel;

import java.util.Objects;

public final class EmployeeEditModel extends SwingEntityEditModel {

  public EmployeeEditModel(EntityConnectionProvider connectionProvider) {
    super(Employee.TYPE, connectionProvider);
    initializeComboBoxModels(Employee.MANAGER_FK, Employee.DEPARTMENT_FK);
    bindEvents();
  }

  /** Providing a custom ComboBoxModel for the manager attribute, which only shows managers and the president */
  @Override
  public EntityComboBoxModel createForeignKeyComboBoxModel(ForeignKey foreignKey) {
    EntityComboBoxModel comboBoxModel = super.createForeignKeyComboBoxModel(foreignKey);
    if (foreignKey.equals(Employee.MANAGER_FK)) {
      //Customize the null value caption so that it displays 'None'
      //instead of the default '-' character
      comboBoxModel.setNullCaption("None");
      //we do not want filtering to remove a value that is selected
      //and thereby change the selection, see bindEvents() below
      comboBoxModel.filterSelectedItem().set(false);
      //Only select the president and managers from the database
      comboBoxModel.condition().set(() ->
              Employee.JOB.in("Manager", "President"));
    }

    return comboBoxModel;
  }

  private void bindEvents() {
    //Refresh the manager ComboBoxModel when an employee is added, deleted or updated,
    //in case a new manager got hired, fired or promoted
    addInsertUpdateOrDeleteListener(() -> foreignKeyComboBoxModel(Employee.MANAGER_FK).refresh());
    //Filter the manager ComboBoxModel so that only managers from the selected department are shown,
    //this filtering happens each time the department value is changed, either when an employee is
    //selected or the department combo box selection changes
    addValueListener(Employee.DEPARTMENT_FK, department -> {
      //only show managers from the same department as the selected employee and hide the currently
      //selected employee to prevent an employee from being made her own manager
      foreignKeyComboBoxModel(Employee.MANAGER_FK).includeCondition().set(manager ->
              Objects.equals(manager.referencedEntity(Employee.DEPARTMENT_FK), department)
                      && !Objects.equals(manager, entity()));
    });
  }
}

7.4. UI

/*
 * This file is part of Codion.
 *
 * Codion is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Codion is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Codion.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright (c) 2004 - 2024, Björn Darri Sigurðsson.
 */
package is.codion.framework.demos.employees.ui;

import is.codion.framework.demos.employees.domain.Employees.Department;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;

import static is.codion.swing.common.ui.component.Components.borderLayoutPanel;
import static is.codion.swing.common.ui.layout.Layouts.borderLayout;

public class DepartmentEditPanel extends EntityEditPanel {

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

  @Override
  protected void initializeUI() {
    initialFocusAttribute().set(Department.DEPARTMENT_NO);

    createTextField(Department.DEPARTMENT_NO)
            .columns(3)
            //don't allow editing of existing department numbers
            .enabled(editModel().exists().not());
    createTextField(Department.NAME)
            .columns(8);
    createTextField(Department.LOCATION)
            .columns(12);

    editModel().exists().addDataListener(exists ->
            initialFocusAttribute().set(exists ? Department.NAME: Department.DEPARTMENT_NO));

    setLayout(borderLayout());
    add(borderLayoutPanel()
            .northComponent(borderLayoutPanel()
                    .westComponent(createInputPanel(Department.DEPARTMENT_NO))
                    .centerComponent(createInputPanel(Department.NAME))
                    .build())
            .centerComponent(createInputPanel(Department.LOCATION))
            .build(), borderLayout().CENTER);
  }
}
/*
 * This file is part of Codion.
 *
 * Codion is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Codion is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Codion.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright (c) 2004 - 2024, Björn Darri Sigurðsson.
 */
package is.codion.framework.demos.employees.ui;

import is.codion.common.state.StateObserver;
import is.codion.framework.demos.employees.domain.Employees.Department;
import is.codion.framework.demos.employees.domain.Employees.Employee;
import is.codion.framework.domain.entity.Entity;
import is.codion.swing.common.ui.control.Control;
import is.codion.swing.common.ui.control.Controls;
import is.codion.swing.common.ui.dialog.Dialogs;
import is.codion.swing.framework.model.SwingEntityTableModel;
import is.codion.swing.framework.ui.EntityTablePanel;

import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.swing.JRViewer;

import java.awt.Dimension;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class DepartmentTablePanel extends EntityTablePanel {

  public DepartmentTablePanel(SwingEntityTableModel tableModel) {
    super(tableModel);
  }

  public void viewEmployeeReport() throws Exception {
    Collection<Integer> departmentNumbers =
            Entity.distinct(Department.DEPARTMENT_NO,
                    tableModel().selectionModel().getSelectedItems());
    Map<String, Object> reportParameters = new HashMap<>();
    reportParameters.put("DEPTNO", departmentNumbers);

    JasperPrint employeeReport = tableModel().connectionProvider().connection()
            .report(Employee.EMPLOYEE_REPORT, reportParameters);

    Dialogs.componentDialog(new JRViewer(employeeReport))
            .owner(this)
            .modal(false)
            .size(new Dimension(800, 600))
            .show();
  }

  @Override
  protected Controls createPrintMenuControls() {
    StateObserver selectionNotEmpty =
            tableModel().selectionModel().selectionNotEmpty();

    return super.createPrintMenuControls()
            .add(Control.builder(this::viewEmployeeReport)
                    .name("Employee Report")
                    .enabled(selectionNotEmpty)
                    .build());
  }
}
/*
 * This file is part of Codion.
 *
 * Codion is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Codion is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Codion.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright (c) 2004 - 2024, Björn Darri Sigurðsson.
 */
package is.codion.framework.demos.employees.ui;

import is.codion.framework.demos.employees.domain.Employees.Department;
import is.codion.framework.demos.employees.domain.Employees.Employee;
import is.codion.swing.common.ui.component.Components;
import is.codion.swing.common.ui.component.text.NumberField;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.ui.EntityEditPanel;
import is.codion.swing.framework.ui.component.EntityComboBox;

import javax.swing.JPanel;

import static is.codion.swing.common.ui.component.Components.gridLayoutPanel;
import static is.codion.swing.common.ui.layout.Layouts.flexibleGridLayout;

public class EmployeeEditPanel extends EntityEditPanel {

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

  @Override
  protected void initializeUI() {
    initialFocusAttribute().set(Employee.NAME);

    createTextField(Employee.NAME)
            .columns(8);
    createItemComboBox(Employee.JOB);
    createForeignKeyComboBox(Employee.MANAGER_FK);
    createTextField(Employee.SALARY)
            .columns(5);
    createTextField(Employee.COMMISSION)
            .columns(5);
    createTemporalFieldPanel(Employee.HIREDATE)
            .columns(6);

    setLayout(flexibleGridLayout(0, 3));

    addInputPanel(Employee.NAME);
    addInputPanel(Employee.DEPARTMENT_FK, createDepartmentPanel());
    addInputPanel(Employee.JOB);

    addInputPanel(Employee.MANAGER_FK);
    add(gridLayoutPanel(1, 2)
            .add(createInputPanel(Employee.SALARY))
            .add(createInputPanel(Employee.COMMISSION))
            .build());
    addInputPanel(Employee.HIREDATE);
  }

  private JPanel createDepartmentPanel() {
    EntityComboBox departmentBox = createForeignKeyComboBox(Employee.DEPARTMENT_FK).build();
    NumberField<Integer> departmentNumberField = departmentBox.integerSelectorField(Department.DEPARTMENT_NO)
            .transferFocusOnEnter(true)
            .build();

    component(Employee.DEPARTMENT_FK).set(departmentNumberField);

    return Components.borderLayoutPanel()
            .westComponent(departmentNumberField)
            .centerComponent(departmentBox)
            .build();
  }
}
/*
 * This file is part of Codion.
 *
 * Codion is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Codion is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Codion.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright (c) 2004 - 2024, Björn Darri Sigurðsson.
 */
package is.codion.framework.demos.employees.ui;

import is.codion.common.user.User;
import is.codion.framework.demos.employees.domain.Employees;
import is.codion.framework.demos.employees.domain.Employees.Department;
import is.codion.framework.demos.employees.domain.Employees.Employee;
import is.codion.framework.demos.employees.model.EmployeesAppModel;
import is.codion.framework.domain.entity.Entity;
import is.codion.swing.common.ui.control.Control;
import is.codion.swing.common.ui.control.Controls;
import is.codion.swing.common.ui.dialog.Dialogs;
import is.codion.swing.common.ui.laf.LookAndFeelProvider;
import is.codion.swing.framework.model.SwingEntityModel;
import is.codion.swing.framework.model.SwingEntityTableModel;
import is.codion.swing.framework.ui.EntityApplicationPanel;
import is.codion.swing.framework.ui.EntityPanel;
import is.codion.swing.framework.ui.EntityTablePanel;

import com.formdev.flatlaf.intellijthemes.FlatAllIJThemes;

import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.Dimension;
import java.io.File;
import java.util.Arrays;
import java.util.List;

import static is.codion.common.Text.textFileContents;
import static is.codion.framework.json.domain.EntityObjectMapper.entityObjectMapper;
import static is.codion.swing.framework.ui.TabbedPanelLayout.splitPaneResizeWeight;
import static java.nio.charset.Charset.defaultCharset;
import static java.util.Collections.singletonList;

public class EmployeesAppPanel extends EntityApplicationPanel<EmployeesAppModel> {

  private static final String DEFAULT_FLAT_LOOK_AND_FEEL = "com.formdev.flatlaf.intellijthemes.FlatArcIJTheme";

  public EmployeesAppPanel(EmployeesAppModel applicationModel) {
    super(applicationModel);
  }

  @Override
  protected List<EntityPanel> createEntityPanels() {
    SwingEntityModel departmentModel = applicationModel().entityModel(Department.TYPE);
    SwingEntityModel employeeModel = departmentModel.detailModel(Employee.TYPE);

    EntityPanel employeePanel = new EntityPanel(employeeModel,
            new EmployeeEditPanel(employeeModel.editModel()));

    EntityPanel departmentPanel = new EntityPanel(departmentModel,
            new DepartmentEditPanel(departmentModel.editModel()),
            new DepartmentTablePanel(departmentModel.tableModel()),
            splitPaneResizeWeight(0.4));
    departmentPanel.addDetailPanel(employeePanel);

    return singletonList(departmentPanel);
  }

  public void importJSON() throws Exception {
    File file = Dialogs.fileSelectionDialog()
            .owner(this)
            .fileFilter(new FileNameExtensionFilter("JSON files", "json"))
            .fileFilter(new FileNameExtensionFilter("Text files", "txt"))
            .selectFile();

    List<Entity> entities = entityObjectMapper(applicationModel().entities())
            .deserializeEntities(textFileContents(file.getAbsolutePath(), defaultCharset()));

    SwingEntityTableModel tableModel = SwingEntityTableModel.tableModel(entities, applicationModel().connectionProvider());
    tableModel.editModel().readOnly().set(true);
    EntityTablePanel tablePanel = new EntityTablePanel(tableModel);
    tablePanel.configure().includePopupMenu(false);

    Dialogs.componentDialog(tablePanel.initialize())
            .owner(this)
            .title("Import")
            .show();
  }

  @Override
  protected Controls createToolsMenuControls() {
    return super.createToolsMenuControls()
            .add(Control.builder(this::importJSON)
                    .name("Import JSON")
                    .build());
  }

  public static void main(String[] args) {
    EntityPanel.TOOLBAR_CONTROLS.set(true);
    Arrays.stream(FlatAllIJThemes.INFOS)
            .forEach(LookAndFeelProvider::addLookAndFeelProvider);
    EntityApplicationPanel.builder(EmployeesAppModel.class, EmployeesAppPanel.class)
            .applicationName("Employees")
            .domainType(Employees.DOMAIN)
            .defaultLookAndFeelClassName(DEFAULT_FLAT_LOOK_AND_FEEL)
            .frameSize(new Dimension(1000, 600))
            .defaultLoginUser(User.parse("scott:tiger"))
            .start();
  }
}