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

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

There is a newer version: 0.18.22
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) 2016 - 2024, Björn Darri Sigurðsson.
 */
package is.codion.framework.model;

import is.codion.common.Conjunction;
import is.codion.common.Operator;
import is.codion.common.event.Event;
import is.codion.common.model.condition.ConditionModel;
import is.codion.common.model.condition.ConditionModel.Operands;
import is.codion.common.model.condition.TableConditionModel;
import is.codion.common.observer.Observer;
import is.codion.common.state.StateObserver;
import is.codion.framework.db.EntityConnectionProvider;
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.Attribute;
import is.codion.framework.domain.entity.attribute.Column;
import is.codion.framework.domain.entity.attribute.ForeignKey;
import is.codion.framework.domain.entity.condition.ColumnCondition;
import is.codion.framework.domain.entity.condition.Condition;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;

import static is.codion.framework.domain.entity.condition.Condition.all;
import static is.codion.framework.domain.entity.condition.Condition.combination;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;

/**
 * A default {@link EntityConditionModel} implementation
 */
final class DefaultEntityConditionModel implements EntityConditionModel {

	private final EntityDefinition entityDefinition;
	private final EntityConnectionProvider connectionProvider;
	private final TableConditionModel> tableConditionModel;
	private final Event conditionChangedEvent = Event.event();
	private final NoneAggregateColumn noneAggregateColumn = new NoneAggregateColumn();
	private final AggregateColumn aggregateColumn = new AggregateColumn();

	DefaultEntityConditionModel(EntityType entityType, EntityConnectionProvider connectionProvider,
															ConditionModelFactory> conditionModelFactory) {
		this.entityDefinition = connectionProvider.entities().definition(requireNonNull(entityType, "entityType"));
		this.connectionProvider = requireNonNull(connectionProvider, "connectionProvider");
		this.tableConditionModel = TableConditionModel.tableConditionModel(createConditionModels(entityType, conditionModelFactory));
		bindEvents();
	}

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

	@Override
	public EntityConnectionProvider connectionProvider() {
		return connectionProvider;
	}

	@Override
	public  boolean setEqualOperand(Attribute attribute, T operand) {
		requireNonNull(attribute);
		boolean aggregate = attribute instanceof Column && entityDefinition.columns().definition((Column) attribute).aggregate();
		Condition condition = aggregate ? having(Conjunction.AND) : where(Conjunction.AND);
		tableConditionModel.optional(attribute)
						.ifPresent(conditionModel -> {
							conditionModel.operator().set(Operator.EQUAL);
							conditionModel.operands().equal().set(operand);
							conditionModel.enabled().set(operand != null);
						});

		return !condition.equals(aggregate ? having(Conjunction.AND) : where(Conjunction.AND));
	}

	@Override
	public  boolean setInOperands(Attribute attribute, Collection operands) {
		requireNonNull(attribute);
		requireNonNull(operands);
		boolean aggregate = attribute instanceof Column && entityDefinition.columns().definition((Column) attribute).aggregate();
		Condition condition = aggregate ? having(Conjunction.AND) : where(Conjunction.AND);
		tableConditionModel.optional(attribute)
						.map(conditionModel -> (ConditionModel) conditionModel)
						.ifPresent(conditionModel -> {
							conditionModel.operator().set(Operator.IN);
							conditionModel.operands().in().set(operands);
							conditionModel.enabled().set(!operands.isEmpty());
						});

		return !condition.equals(aggregate ? having(Conjunction.AND) : where(Conjunction.AND));
	}

	@Override
	public Condition where(Conjunction conjunction) {
		return createCondition(conjunction, noneAggregateColumn);
	}

	@Override
	public Condition having(Conjunction conjunction) {
		return createCondition(conjunction, aggregateColumn);
	}

	@Override
	public Map, ConditionModel> get() {
		return tableConditionModel.get();
	}

	@Override
	public  ConditionModel get(Attribute identifier) {
		return tableConditionModel.get(requireNonNull(identifier));
	}

	@Override
	public  Optional> optional(Attribute identifier) {
		return tableConditionModel.optional(requireNonNull(identifier));
	}

	@Override
	public  ConditionModel attribute(Attribute attribute) {
		return get(attribute);
	}

	@Override
	public StateObserver enabled() {
		return tableConditionModel.enabled();
	}

	@Override
	public Observer changed() {
		return tableConditionModel.changed();
	}

	@Override
	public void clear() {
		tableConditionModel.clear();
	}

	private Condition createCondition(Conjunction conjunction, Predicate> columnType) {
		List conditions = tableConditionModel.get().entrySet().stream()
						.filter(entry -> columnType.test(entry.getKey()))
						.filter(entry -> entry.getValue().enabled().get())
						.map(entry -> condition(entry.getValue(), entry.getKey()))
						.collect(toList());
		switch (conditions.size()) {
			case 0:
				return all(entityDefinition.entityType());
			case 1:
				return conditions.get(0);
			default:
				return combination(conjunction, conditions);
		}
	}

	private void bindEvents() {
		tableConditionModel.get().values()
						.forEach(conditionModel -> conditionModel.changed().addListener(conditionChangedEvent));
	}

	private Map, ConditionModel> createConditionModels(EntityType entityType,
																																		 ConditionModelFactory> conditionModelFactory) {
		Map, ConditionModel> models = new HashMap<>();
		EntityDefinition definition = connectionProvider.entities().definition(entityType);
		definition.columns().definitions().forEach(columnDefinition ->
						conditionModelFactory.create(columnDefinition.attribute())
										.ifPresent(conditionModel -> models.put(columnDefinition.attribute(), conditionModel)));
		definition.foreignKeys().definitions().forEach(foreignKeyDefinition ->
						conditionModelFactory.create(foreignKeyDefinition.attribute())
										.ifPresent(conditionModel -> models.put(foreignKeyDefinition.attribute(), conditionModel)));

		return models;
	}

	private static Condition condition(ConditionModel conditionModel, Attribute identifier) {
		if (identifier instanceof ForeignKey) {
			return foreignKeyCondition((ConditionModel) conditionModel, (ForeignKey) identifier);
		}

		return columnCondition(conditionModel, identifier);
	}

	private static Condition foreignKeyCondition(ConditionModel conditionModel, ForeignKey foreignKey) {
		Entity equalOperand = conditionModel.operands().equal().get();
		Collection inOperands = conditionModel.operands().in().get();
		switch (conditionModel.operator().get()) {
			case EQUAL:
				return equalOperand == null ? foreignKey.isNull() : foreignKey.equalTo(equalOperand);
			case IN:
				return inOperands.isEmpty() ? foreignKey.isNull() : foreignKey.in(inOperands);
			case NOT_EQUAL:
				return equalOperand == null ? foreignKey.isNotNull() : foreignKey.notEqualTo(equalOperand);
			case NOT_IN:
				return inOperands.isEmpty() ? foreignKey.isNotNull() : foreignKey.notIn(inOperands);
			default:
				throw new IllegalArgumentException("Unsupported operator: " + conditionModel.operator().get() + " for foreign key condition");
		}
	}

	private static  ColumnCondition columnCondition(ConditionModel conditionModel, Attribute identifier) {
		Column column = (Column) identifier;
		Operands operands = conditionModel.operands();
		switch (conditionModel.operator().get()) {
			case EQUAL:
				return equalCondition(conditionModel, column);
			case NOT_EQUAL:
				return notEqualCondition(conditionModel, column);
			case LESS_THAN:
				return column.lessThan(operands.upperBound().get());
			case LESS_THAN_OR_EQUAL:
				return column.lessThanOrEqualTo(operands.upperBound().get());
			case GREATER_THAN:
				return column.greaterThan(operands.lowerBound().get());
			case GREATER_THAN_OR_EQUAL:
				return column.greaterThanOrEqualTo(operands.lowerBound().get());
			case BETWEEN_EXCLUSIVE:
				return column.betweenExclusive(operands.lowerBound().get(), operands.upperBound().get());
			case BETWEEN:
				return column.between(operands.lowerBound().get(), operands.upperBound().get());
			case NOT_BETWEEN_EXCLUSIVE:
				return column.notBetweenExclusive(operands.lowerBound().get(), operands.upperBound().get());
			case NOT_BETWEEN:
				return column.notBetween(operands.lowerBound().get(), operands.upperBound().get());
			case IN:
				return inCondition(conditionModel, column);
			case NOT_IN:
				return notInCondition(conditionModel, column);
			default:
				throw new IllegalArgumentException("Unknown operator: " + conditionModel.operator().get());
		}
	}

	private static  ColumnCondition equalCondition(ConditionModel conditionModel,
																											 Column column) {
		T equalOperand = conditionModel.operands().equal().get();
		if (equalOperand == null) {
			return column.isNull();
		}
		if (column.type().isString()) {
			return singleStringEqualCondition(conditionModel, column, (String) equalOperand);
		}
		if (column.type().isCharacter()) {
			return singleCharacterEqualCondition(conditionModel, column, (Character) equalOperand);
		}

		return column.equalTo(equalOperand);
	}

	private static  ColumnCondition notEqualCondition(ConditionModel conditionModel,
																													Column column) {
		T equalOperand = conditionModel.operands().equal().get();
		if (equalOperand == null) {
			return column.isNotNull();
		}
		if (column.type().isString()) {
			return singleStringNotEqualCondition(conditionModel, column, (String) equalOperand);
		}
		if (column.type().isCharacter()) {
			return singleCharacterNotEqualCondition(conditionModel, column, (Character) equalOperand);
		}

		return column.notEqualTo(equalOperand);
	}

	private static  ColumnCondition singleStringEqualCondition(ConditionModel conditionModel,
																																	 Column column, String value) {
		boolean caseSensitive = conditionModel.caseSensitive().get();
		if (containsWildcards(value)) {
			return (ColumnCondition) (caseSensitive ? column.like(value) : column.likeIgnoreCase(value));
		}

		return caseSensitive ? column.equalTo((T) value) : (ColumnCondition) column.equalToIgnoreCase(value);
	}

	private static  ColumnCondition singleCharacterEqualCondition(ConditionModel conditionModel,
																																			Column column, Character value) {
		return conditionModel.caseSensitive().get() ? column.equalTo((T) value) : (ColumnCondition) column.equalToIgnoreCase(value);
	}

	private static  ColumnCondition singleStringNotEqualCondition(ConditionModel conditionModel,
																																			Column column, String value) {
		boolean caseSensitive = conditionModel.caseSensitive().get();
		if (containsWildcards(value)) {
			return (ColumnCondition) (caseSensitive ? column.notLike(value) : column.notLikeIgnoreCase(value));
		}

		return caseSensitive ? column.notEqualTo((T) value) : (ColumnCondition) column.notEqualToIgnoreCase(value);
	}

	private static  ColumnCondition singleCharacterNotEqualCondition(ConditionModel conditionModel,
																																				 Column column, Character value) {
		return conditionModel.caseSensitive().get() ? column.notEqualTo((T) value) : (ColumnCondition) column.notEqualToIgnoreCase(value);
	}

	private static  ColumnCondition inCondition(ConditionModel conditionModel, Column column) {
		if (column.type().isString()) {
			Column stringColumn = (Column) column;
			Collection inOperands = (Collection) conditionModel.operands().in().get();

			return (ColumnCondition) (conditionModel.caseSensitive().get() ?
							stringColumn.in(inOperands) :
							stringColumn.inIgnoreCase(inOperands));
		}

		return column.in(conditionModel.operands().in().get());
	}

	private static  ColumnCondition notInCondition(ConditionModel conditionModel, Column column) {
		if (column.type().isString()) {
			Column stringColumn = (Column) column;
			Collection inOperands = (Collection) conditionModel.operands().in().get();

			return (ColumnCondition) (conditionModel.caseSensitive().get() ?
							stringColumn.notIn(inOperands) :
							stringColumn.notInIgnoreCase(inOperands));
		}

		return column.notIn(conditionModel.operands().in().get());
	}

	private static boolean containsWildcards(String value) {
		return value != null && (value.contains("%") || value.contains("_"));
	}

	private final class AggregateColumn implements Predicate> {

		@Override
		public boolean test(Attribute attribute) {
			return (attribute instanceof Column) &&
							entityDefinition.columns().definition((Column) attribute).aggregate();
		}
	}

	private final class NoneAggregateColumn implements Predicate> {

		@Override
		public boolean test(Attribute attribute) {
			return !(attribute instanceof Column) ||
							!entityDefinition.columns().definition((Column) attribute).aggregate();
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy