All Downloads are FREE. Search and download functionalities are using the official Maven repository.

is.codion.framework.model.DefaultEntityModel Maven / Gradle / Ivy

There is a newer version: 0.18.25
Show newest version
/*
 * 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 .
 *
 * Copyright (c) 2008 - 2024, Björn Darri Sigurðsson.
 */
package is.codion.framework.model;

import is.codion.common.value.ValueSet;
import is.codion.common.value.ValueSetObserver;
import is.codion.framework.db.EntityConnection;
import is.codion.framework.db.EntityConnectionProvider;
import is.codion.framework.domain.entity.Entities;
import is.codion.framework.domain.entity.Entity;
import is.codion.framework.domain.entity.EntityDefinition;
import is.codion.framework.domain.entity.EntityType;
import is.codion.framework.domain.entity.attribute.ForeignKey;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static java.util.Collections.*;
import static java.util.Objects.requireNonNull;

/**
 * A default EntityModel implementation.
 * @param  the type of {@link DefaultEntityModel} used for detail models
 * @param  the type of {@link AbstractEntityEditModel} used by this {@link EntityModel}
 * @param  the type of {@link EntityTableModel} used by this {@link EntityModel}
 */
public class DefaultEntityModel, E extends AbstractEntityEditModel,
				T extends EntityTableModel> implements EntityModel {

	private static final String DETAIL_MODEL_PARAMETER = "detailModel";

	private final E editModel;
	private final T tableModel;
	private final Map> detailModels = new HashMap<>();
	private final ValueSet linkedDetailModels = ValueSet.valueSet();

	/**
	 * Instantiates a new DefaultEntityModel, without a table model
	 * @param editModel the edit model
	 */
	public DefaultEntityModel(E editModel) {
		requireNonNull(editModel, "editModel");
		this.editModel = editModel;
		this.tableModel = null;
		bindEventsInternal();
	}

	/**
	 * Instantiates a new DefaultEntityModel
	 * @param tableModel the table model
	 */
	public DefaultEntityModel(T tableModel) {
		requireNonNull(tableModel, "tableModel");
		this.editModel = tableModel.editModel();
		this.tableModel = tableModel;
		bindEventsInternal();
	}

	/**
	 * @return a String representation of this EntityModel,
	 * returns the model class name by default
	 */
	@Override
	public final String toString() {
		return getClass().getSimpleName() + ": " + entityType();
	}

	@Override
	public final EntityType entityType() {
		return editModel.entityType();
	}

	@Override
	public final EntityConnectionProvider connectionProvider() {
		return editModel.connectionProvider();
	}

	@Override
	public final EntityConnection connection() {
		return editModel.connection();
	}

	@Override
	public final Entities entities() {
		return editModel.connectionProvider().entities();
	}

	@Override
	public final EntityDefinition entityDefinition() {
		return editModel.entityDefinition();
	}

	@Override
	public final  C editModel() {
		return (C) editModel;
	}

	@Override
	public final  C tableModel() {
		if (tableModel == null) {
			throw new IllegalStateException("Entity model " + this + " does not contain a table model");
		}

		return (C) tableModel;
	}

	@Override
	public final boolean containsTableModel() {
		return tableModel != null;
	}

	@Override
	@SafeVarargs
	public final void addDetailModels(M... detailModels) {
		requireNonNull(detailModels, "detailModels");
		for (M detailModel : detailModels) {
			addDetailModel(detailModel);
		}
	}

	@Override
	public final ForeignKeyDetailModelLink addDetailModel(M detailModel) {
		requireNonNull(detailModel, DETAIL_MODEL_PARAMETER);
		Collection foreignKeys = detailModel.editModel().entityDefinition().foreignKeys().get(editModel.entityType());
		if (foreignKeys.isEmpty()) {
			throw new IllegalArgumentException("Entity " + detailModel.editModel().entityType() +
							" does not reference " + editModel.entityType() + " via a foreign key");
		}

		return addDetailModel(detailModel, foreignKeys.iterator().next());
	}

	@Override
	public final ForeignKeyDetailModelLink addDetailModel(M detailModel, ForeignKey foreignKey) {
		requireNonNull(detailModel, DETAIL_MODEL_PARAMETER);
		requireNonNull(foreignKey, "foreignKey");

		return addDetailModel(new DefaultForeignKeyDetailModelLink<>(detailModel, foreignKey));
	}

	@Override
	public final > L addDetailModel(L detailModelLink) {
		requireNonNull(detailModelLink, "detailModelLink");
		if (this == detailModelLink.detailModel()) {
			throw new IllegalArgumentException("A model can not be its own detail model");
		}
		if (detailModels.containsKey(detailModelLink.detailModel())) {
			throw new IllegalArgumentException("Detail model " + detailModelLink.detailModel() + " has already been added");
		}
		detailModels.put(detailModelLink.detailModel(), detailModelLink);
		detailModelLink.active().addConsumer(new ActiveDetailModelConsumer(detailModelLink));

		return detailModelLink;
	}

	@Override
	public final boolean containsDetailModel(Class modelClass) {
		requireNonNull(modelClass, "modelClass");
		return detailModels.keySet().stream()
						.anyMatch(detailModel -> detailModel.getClass().equals(modelClass));
	}

	@Override
	public final boolean containsDetailModel(EntityType entityType) {
		requireNonNull(entityType, "entityType");
		return detailModels.keySet().stream()
						.anyMatch(detailModel -> detailModel.entityType().equals(entityType));
	}

	@Override
	public final boolean containsDetailModel(M detailModel) {
		return detailModels.containsKey(requireNonNull(detailModel, DETAIL_MODEL_PARAMETER));
	}

	@Override
	public final Collection detailModels() {
		return unmodifiableCollection(detailModels.keySet());
	}

	@Override
	public final > L detailModelLink(M detailModel) {
		if (!detailModels.containsKey(requireNonNull(detailModel))) {
			throw new IllegalStateException("Detail model not found: " + detailModel);
		}

		return (L) detailModels.get(detailModel);
	}

	@Override
	public final ValueSetObserver linkedDetailModels() {
		return linkedDetailModels.observer();
	}

	@Override
	public final  C detailModel(Class modelClass) {
		requireNonNull(modelClass, "modelClass");
		return (C) detailModels.keySet().stream()
						.filter(detailModel -> detailModel.getClass().equals(modelClass))
						.findFirst()
						.orElseThrow(() -> new IllegalArgumentException("Detail model of type " + modelClass.getName() + " not found in model: " + this));
	}

	@Override
	public final  C detailModel(EntityType entityType) {
		requireNonNull(entityType, "entityType");
		return (C) detailModels.keySet().stream()
						.filter(detailModel -> detailModel.entityType().equals(entityType))
						.findFirst()
						.orElseThrow(() -> new IllegalArgumentException("No detail model for entity " + entityType + " found in model: " + this));
	}

	private void onMasterSelectionChanged() {
		if (!linkedDetailModels().empty()) {
			List activeEntities = activeEntities();
			for (M detailModel : linkedDetailModels()) {
				detailModels.get(detailModel).onSelection(activeEntities);
			}
		}
	}

	private List activeEntities() {
		if (tableModel != null && tableModel.selection().empty().not().get()) {
			return tableModel.selection().items().get();
		}
		else if (editModel.entity().exists().not().get()) {
			return emptyList();
		}

		return singletonList(editModel.entity().get());
	}

	private void bindEventsInternal() {
		editModel.afterInsert().addConsumer(this::onInsert);
		editModel.afterUpdate().addConsumer(this::onUpdate);
		editModel.afterDelete().addConsumer(this::onDelete);
		if (containsTableModel()) {
			tableModel.selection().indexes().addListener(this::onMasterSelectionChanged);
		}
		else {
			editModel.entity().addListener(this::onMasterSelectionChanged);
		}
	}

	private void onInsert(Collection insertedEntities) {
		detailModels.keySet().forEach(detailModel -> detailModels.get(detailModel).onInsert(insertedEntities));
	}

	private void onUpdate(Map updatedEntities) {
		detailModels.keySet().forEach(detailModel -> detailModels.get(detailModel).onUpdate(updatedEntities));
	}

	private void onDelete(Collection deletedEntities) {
		detailModels.keySet().forEach(detailModel -> detailModels.get(detailModel).onDelete(deletedEntities));
	}

	private final class ActiveDetailModelConsumer implements Consumer {

		private final DetailModelLink detailModelLink;

		private ActiveDetailModelConsumer(DetailModelLink detailModelLink) {
			this.detailModelLink = detailModelLink;
		}

		@Override
		public void accept(Boolean active) {
			linkedDetailModels.set(detailModels.values().stream()
							.filter(link -> link.active().get())
							.map(DetailModelLink::detailModel)
							.collect(Collectors.toList()));
			if (active) {
				detailModelLink.onSelection(activeEntities());
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy